[CalendarServer-changes] [2556] CalendarServer/branches/users/cdaboo/server2server-2524/ twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Fri Jun 13 16:42:18 PDT 2008


Revision: 2556
          http://trac.macosforge.org/projects/calendarserver/changeset/2556
Author:   cdaboo at apple.com
Date:     2008-06-13 16:42:17 -0700 (Fri, 13 Jun 2008)

Log Message:
-----------
Basic outbound iMIP scheduling via the server. Currently hard-coded in place of server-to-server.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/__init__.py
    CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/schedule_common.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/schedule_imip.py
    CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/test/test_imip.py

Modified: CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/__init__.py	2008-06-12 18:47:55 UTC (rev 2555)
+++ CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/__init__.py	2008-06-13 23:42:17 UTC (rev 2556)
@@ -48,6 +48,7 @@
     "root",
     "schedule",
     "schedule_common",
+    "schedule_imip",
     "servertoserver",
     "servertoserverparser",
     "sql",

Modified: CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/schedule_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/schedule_common.py	2008-06-12 18:47:55 UTC (rev 2555)
+++ CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/schedule_common.py	2008-06-13 23:42:17 UTC (rev 2556)
@@ -46,6 +46,7 @@
 from twistedcaldav.method import report_common
 from twistedcaldav.method.put_common import StoreCalendarObjectResource
 from twistedcaldav.resource import isCalendarCollectionResource
+from twistedcaldav.schedule_imip import ServerToIMip
 from twistedcaldav.servertoserver import ServerToServer
 from twistedcaldav.servertoserver import ServerToServerRequest
 import itertools
@@ -385,7 +386,8 @@
     
         # Now process remote recipients
         if remote_recipients:
-            yield self.generateRemoteSchedulingResponses(remote_recipients, responses)
+            #yield self.generateRemoteSchedulingResponses(remote_recipients, responses, freebusy)
+            yield self.generateIMIPSchedulingResponses(remote_recipients, responses, freebusy)
 
         # Now we have to do auto-respond
         if len(autoresponses) != 0:
@@ -403,7 +405,7 @@
         returnValue(responses.response())
     
     @inlineCallbacks
-    def generateRemoteSchedulingResponses(self, recipients, responses):
+    def generateRemoteSchedulingResponses(self, recipients, responses, freebusy):
         """
         Generate scheduling responses for remote recipients.
         """
@@ -447,6 +449,19 @@
         yield DeferredList(deferreds)
 
     @inlineCallbacks
+    def generateIMIPSchedulingResponses(self, recipients, responses, freebusy):
+        """
+        Generate scheduling responses for iMIP recipients.
+        """
+        
+        # Now we process each server: let's use a DeferredList to aggregate all the Deferred's
+        # we will generate for each request. That way we can have parallel requests in progress
+        # rather than serialize them.
+        
+        requestor = ServerToIMip(self, recipients, responses)
+        yield requestor.doEMail(freebusy)
+
+    @inlineCallbacks
     def generateLocalResponse(self, recipient, responses, autoresponses):
         # Hash the iCalendar data for use as the last path element of the URI path
         calendar_str = str(self.calendar)
@@ -568,12 +583,6 @@
         )
 
         returnValue(fbresult)
-    
-    def generateRemoteResponse(self):
-        raise NotImplementedError
-    
-    def generateRemoteFreeBusyResponse(self):
-        raise NotImplementedError
         
 class CalDAVScheduler(Scheduler):
 

Added: CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/schedule_imip.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/schedule_imip.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/schedule_imip.py	2008-06-13 23:42:17 UTC (rev 2556)
@@ -0,0 +1,254 @@
+##
+# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+from twisted.mail.smtp import messageid
+from twisted.mail.smtp import rfc822date
+from twisted.mail.smtp import sendmail
+import datetime
+import base64
+import MimeWriter
+import cStringIO
+
+"""
+Server to iMIP scheduling functions.
+"""
+
+__all__ = [
+    "ServerToIMip",
+]
+
+from twisted.internet.defer import inlineCallbacks
+from twisted.python.failure import Failure
+from twisted.web2 import responsecode
+from twisted.web2.dav.http import ErrorResponse
+from twisted.web2.http import HTTPError
+
+from twistedcaldav.caldavxml import caldav_namespace
+from twistedcaldav.log import Logger
+
+log = Logger()
+
+class ServerToIMip(object):
+    
+    def __init__(self, scheduler, recipients, responses):
+
+        self.scheduler = scheduler
+        self.recipients = recipients
+        self.responses = responses
+        
+    @inlineCallbacks
+    def doEMail(self, freebusy):
+        
+        # Generate an HTTP client request
+        try:
+            # We do not do freebusy requests via iMIP
+            if freebusy:
+                raise ValueError("iMIP VFREEBUSY REQUESTs not supported.")
+
+            message = self._generateTemplateMessage(self.scheduler.calendar)
+            fromAddr = self.scheduler.originator.cuaddr
+            if not fromAddr.startswith("mailto:"):
+                raise ValueError("ORGANIZER address '%s' must be mailto: for iMIP operation." % (fromAddr,))
+            fromAddr = fromAddr[7:]
+            message = message.replace("${fromaddress}", fromAddr)
+            
+            for recipient in self.recipients:
+                try:
+                    toAddr = recipient.cuaddr
+                    if not toAddr.startswith("mailto:"):
+                        raise ValueError("ATTENDEE address '%s' must be mailto: for iMIP operation." % (toAddr,))
+                    toAddr = toAddr[7:]
+                    sendit = message.replace("${toaddress}", toAddr)
+                    yield sendmail("relay.apple.com", fromAddr, toAddr, sendit)
+        
+                except Exception, e:
+                    # Generated failed response for this recipient
+                    log.err("Could not do server-to-imip request : %s %s" % (self, e))
+                    err = HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-failed")))
+                    self.responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="5.1;Service unavailable")
+                
+                else:
+                    self.responses.add(recipient.cuaddr, responsecode.OK, reqstatus="2.0;Success")
+
+        except Exception, e:
+            # Generated failed responses for each recipient
+            log.err("Could not do server-to-server request : %s %s" % (self, e))
+            for recipient in self.recipients:
+                err = HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-failed")))
+                self.responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="5.1;Service unavailable")
+
+    def _generateTemplateMessage(self, calendar):
+
+        caldata = str(calendar)
+        data = cStringIO.StringIO()
+        writer = MimeWriter.MimeWriter(data)
+    
+        writer.addheader("From", "${fromaddress}")
+        writer.addheader("To", "${toaddress}")
+        writer.addheader("Date", rfc822date())
+        writer.addheader("Subject", "DO NOT REPLY: calendar invitation test")
+        writer.addheader("Message-ID", messageid())
+        writer.addheader("Mime-Version", "1.0")
+        writer.flushheaders()
+    
+        writer.startmultipartbody("mixed")
+    
+        # message body
+        part = writer.nextpart()
+        body = part.startbody("text/plain")
+        body.write("""Hi,
+You've been invited to a cool event by CalendarServer's new iMIP processor.
+
+%s
+""" % (self._generateCalendarSummary(calendar),))
+    
+        part = writer.nextpart()
+        encoding = "7bit"
+        for i in caldata:
+            if ord(i) > 127:
+                encoding = "base64"
+                caldata = base64.encodestring(caldata)
+                break
+        part.addheader("Content-Transfer-Encoding", encoding)
+        body = part.startbody("text/calendar; charset=utf-8")
+        body.write(caldata.replace("\r", ""))
+    
+        # finish
+        writer.lastpart()
+
+        return data.getvalue()
+
+    def _generateCalendarSummary(self, calendar):
+
+        # Get the most appropriate component
+        component = calendar.masterComponent()
+        if component is None:
+            component = calendar.mainComponent(True)
+            
+        organizer = component.getOrganizerProperty()
+        if "CN" in organizer.params():
+            organizer = "%s <%s>" % (organizer.params()["CN"][0], organizer.value(),)
+        else:
+            organizer = organizer.value()
+            
+        dtinfo = self._getDateTimeInfo(component)
+        
+        summary = component.propertyValue("SUMMARY")
+        if summary is None:
+            summary = ""
+
+        description = component.propertyValue("DESCRIPTION")
+        if description is None:
+            description = ""
+
+        return """---- Begin Calendar Event Summary ----
+
+Organizer:   %s
+Summary:     %s
+%sDescription: %s
+
+----  End Calendar Event Summary  ----
+""" % (organizer, summary, dtinfo, description,)
+
+    def _getDateTimeInfo(self, component):
+        
+        dtstart = component.propertyNativeValue("DTSTART")
+        tzid_start = component.getProperty("DTSTART").params().get("TZID", "UTC")
+
+        dtend = component.propertyNativeValue("DTEND")
+        if dtend:
+            tzid_end = component.getProperty("DTEND").params().get("TZID", "UTC")
+            duration = dtend - dtstart
+        else:
+            duration = component.propertyNativeValue("DURATION")
+            if duration:
+                dtend = dtstart + duration
+                tzid_end = tzid_start
+            else:
+                if isinstance(dtstart, datetime.date):
+                    dtend = None
+                    duration = datetime.timedelta(days=1)
+                else:
+                    dtend = dtstart + datetime.timedelta(days=1)
+                    dtend.hour = dtend.minute = dtend.second = 0
+                    duration = dtend - dtstart
+        result = "Starts:      %s\n" % (self._getDateTimeText(dtstart, tzid_start),)
+        if dtend is not None:
+            result += "Ends:        %s\n" % (self._getDateTimeText(dtend, tzid_end),)
+        result += "Duration:    %s\n" % (self._getDurationText(duration),)
+        
+        if not isinstance(dtstart, datetime.datetime):
+            result += "All Day\n"
+        
+        for property_name in ("RRULE", "RDATE", "EXRULE", "EXDATE", "RECURRENCE-ID",):
+            if component.hasProperty(property_name):
+                result += "Recurring\n"
+                break
+            
+        return result
+
+    def _getDateTimeText(self, dtvalue, tzid):
+        
+        if isinstance(dtvalue, datetime.datetime):
+            timeformat = "%A, %B %e, %Y %I:%M %p"
+        elif isinstance(dtvalue, datetime.date):
+            timeformat = "%A, %B %e, %Y"
+            tzid = ""
+        if tzid:
+            tzid = " (%s)" % (tzid,)
+
+        return "%s%s" % (dtvalue.strftime(timeformat), tzid,)
+        
+    def _getDurationText(self, duration):
+        
+        result = ""
+        if duration.days > 0:
+            result += "%d %s" % (
+                duration.days,
+                self._pluralize(duration.days, "day", "days")
+            )
+
+        hours = duration.seconds / 3600
+        minutes = divmod(duration.seconds / 60, 60)[1]
+        seconds = divmod(duration.seconds, 60)[1]
+        
+        if hours > 0:
+            if result:
+                result += ", "
+            result += "%d %s" % (
+                hours,
+                self._pluralize(hours, "hour", "hours")
+            )
+        
+        if minutes > 0:
+            if result:
+                result += ", "
+            result += "%d %s" % (
+                minutes,
+                self._pluralize(minutes, "minute", "minutes")
+            )
+        
+        if seconds > 0:
+            if result:
+                result += ", "
+            result += "%d %s" % (
+                seconds,
+                self._pluralize(seconds, "second", "seconds")
+            )
+
+        return result
+
+    def _pluralize(self, number, unit1, unitS):
+        return unit1 if number == 1 else unitS

Added: CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/test/test_imip.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/test/test_imip.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/server2server-2524/twistedcaldav/test/test_imip.py	2008-06-13 23:42:17 UTC (rev 2556)
@@ -0,0 +1,500 @@
+##
+# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+from twistedcaldav.ical import Component
+
+import datetime
+
+from twistedcaldav.schedule_imip import ServerToIMip
+import twistedcaldav.test.util
+
+class iMIP (twistedcaldav.test.util.TestCase):
+    """
+    iCalendar support tests
+    """
+
+    class DummyScheduler(object):
+        def __init__(self, calendar):
+            self.calendar = calendar
+
+    def test_datetime_text(self):
+        
+        imip = ServerToIMip(None, [], [])
+        
+        data = (
+            (
+                datetime.datetime(2008, 06, 01, 12, 0, 0),
+                "America/New_York",
+                "Sunday, June  1, 2008 12:00 PM (America/New_York)",
+            ),
+            (
+                datetime.date(2008, 06, 02),
+                "",
+                "Monday, June  2, 2008",
+            ),
+        )
+        
+        for dt, tzid, result in data:
+            self.assertEqual(imip._getDateTimeText(dt, tzid), result)
+        
+    def test_duration_text(self):
+        
+        imip = ServerToIMip(None, [], [])
+        
+        data = (
+            (
+                datetime.timedelta(days=1),
+                "1 day",
+            ),
+            (
+                datetime.timedelta(days=2),
+                "2 days",
+            ),
+            (
+                datetime.timedelta(seconds=1*60*60),
+                "1 hour",
+            ),
+            (
+                datetime.timedelta(seconds=2*60*60),
+                "2 hours",
+            ),
+            (
+                datetime.timedelta(seconds=1*60),
+                "1 minute",
+            ),
+            (
+                datetime.timedelta(seconds=2*60),
+                "2 minutes",
+            ),
+            (
+                datetime.timedelta(seconds=1),
+                "1 second",
+            ),
+            (
+                datetime.timedelta(seconds=2),
+                "2 seconds",
+            ),
+            (
+                datetime.timedelta(days=1, seconds=1*60*60),
+                "1 day, 1 hour",
+            ),
+            (
+                datetime.timedelta(days=1, seconds=1*60),
+                "1 day, 1 minute",
+            ),
+            (
+                datetime.timedelta(days=1, seconds=1),
+                "1 day, 1 second",
+            ),
+            (
+                datetime.timedelta(days=1, seconds=1*60*60 + 2*60),
+                "1 day, 1 hour, 2 minutes",
+            ),
+            (
+                datetime.timedelta(seconds=2*60*60 + 15*60),
+                "2 hours, 15 minutes",
+            ),
+        )
+        
+        for duration, result in data:
+            self.assertEqual(imip._getDurationText(duration), result)
+
+    def test_datetime_info(self):
+        data = (
+                   (
+                       """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+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:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                        """Starts:      Sunday, June  1, 2008 12:00 PM (UTC)
+Ends:        Sunday, June  1, 2008 01:00 PM (UTC)
+Duration:    1 hour
+""",
+                    ),
+                   (
+                       """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;VALUE=DATE:20080601
+DTEND;VALUE=DATE:20080602
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                        """Starts:      Sunday, June  1, 2008
+Ends:        Monday, June  2, 2008
+Duration:    1 day
+All Day
+""",
+                    ),
+                   (
+                       """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;VALUE=DATE:20080601
+DTEND;VALUE=DATE:20080602
+RRULE:FREQ=YEARLY
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                        """Starts:      Sunday, June  1, 2008
+Ends:        Monday, June  2, 2008
+Duration:    1 day
+All Day
+Recurring
+""",
+                    ),
+                )
+        
+        
+        for data, result in data:
+            imip = ServerToIMip(self.DummyScheduler(Component.fromString(data)), [], [])
+            self.assertEqual(imip._getDateTimeInfo(imip.scheduler.calendar.masterComponent()), result)
+        
+    def test_calendar_summary(self):
+        data = (
+                   (
+                       """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+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:mailto:user2 at example.com
+SUMMARY:This is an event
+END:VEVENT
+END:VCALENDAR
+""",
+                        """---- Begin Calendar Event Summary ----
+
+Organizer:   User 01 <mailto:user1 at example.com>
+Summary:     This is an event
+Starts:      Sunday, June  1, 2008 12:00 PM (UTC)
+Ends:        Sunday, June  1, 2008 01:00 PM (UTC)
+Duration:    1 hour
+Description: 
+
+----  End Calendar Event Summary  ----
+""",
+                    ),
+                   (
+                       """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;VALUE=DATE:20080601
+DTEND;VALUE=DATE:20080602
+ORGANIZER;CN="User 02":mailto:user2 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+SUMMARY:This is an event
+DESCRIPTION:Testing iMIP from the calendar server.
+END:VEVENT
+END:VCALENDAR
+""",
+                        """---- Begin Calendar Event Summary ----
+
+Organizer:   User 02 <mailto:user2 at example.com>
+Summary:     This is an event
+Starts:      Sunday, June  1, 2008
+Ends:        Monday, June  2, 2008
+Duration:    1 day
+All Day
+Description: Testing iMIP from the calendar server.
+
+----  End Calendar Event Summary  ----
+""",
+                    ),
+                   (
+                       """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;VALUE=DATE:20080601
+DTEND;VALUE=DATE:20080602
+RRULE:FREQ=YEARLY
+ORGANIZER;CN="User 03":mailto:user3 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+SUMMARY:This is an event
+DESCRIPTION:Testing iMIP from the calendar server.
+END:VEVENT
+END:VCALENDAR
+""",
+                        """---- Begin Calendar Event Summary ----
+
+Organizer:   User 03 <mailto:user3 at example.com>
+Summary:     This is an event
+Starts:      Sunday, June  1, 2008
+Ends:        Monday, June  2, 2008
+Duration:    1 day
+All Day
+Recurring
+Description: Testing iMIP from the calendar server.
+
+----  End Calendar Event Summary  ----
+""",
+                    ),
+                )
+        
+        
+        for data, result in data:
+            imip = ServerToIMip(self.DummyScheduler(Component.fromString(data)), [], [])
+            self.assertEqual(imip._generateCalendarSummary(imip.scheduler.calendar), result)
+        
+        
+    def test_template_message(self):
+        data = (
+                   (
+                       """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+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:mailto:user2 at example.com
+SUMMARY:This is an event
+END:VEVENT
+END:VCALENDAR
+""",
+                        """From: ${fromaddress}
+To: ${toaddress}
+Subject: DO NOT REPLY: calendar invitation test
+Mime-Version: 1.0
+Content-Type: multipart/mixed;
+    boundary="boundary"
+
+
+--boundary
+Content-Type: text/plain
+
+Hi,
+You've been invited to a cool event by CalendarServer's new iMIP processor.
+
+---- Begin Calendar Event Summary ----
+
+Organizer:   User 01 <mailto:user1 at example.com>
+Summary:     This is an event
+Starts:      Sunday, June  1, 2008 12:00 PM (UTC)
+Ends:        Sunday, June  1, 2008 01:00 PM (UTC)
+Duration:    1 hour
+Description: 
+
+----  End Calendar Event Summary  ----
+
+
+--boundary
+Content-Type: text/calendar; charset=utf-8
+Content-Transfer-Encoding: 7bit
+
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+SUMMARY:This is an event
+END:VEVENT
+END:VCALENDAR
+
+--boundary--
+""",
+                    ),
+                   (
+                       """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;VALUE=DATE:20080601
+DTEND;VALUE=DATE:20080602
+ORGANIZER;CN=User 02:mailto:user2 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+SUMMARY:This is an event
+DESCRIPTION:Testing iMIP from the calendar server.
+END:VEVENT
+END:VCALENDAR
+""",
+                        """From: ${fromaddress}
+To: ${toaddress}
+Subject: DO NOT REPLY: calendar invitation test
+Mime-Version: 1.0
+Content-Type: multipart/mixed;
+    boundary="boundary"
+
+
+--boundary
+Content-Type: text/plain
+
+Hi,
+You've been invited to a cool event by CalendarServer's new iMIP processor.
+
+---- Begin Calendar Event Summary ----
+
+Organizer:   User 02 <mailto:user2 at example.com>
+Summary:     This is an event
+Starts:      Sunday, June  1, 2008
+Ends:        Monday, June  2, 2008
+Duration:    1 day
+All Day
+Description: Testing iMIP from the calendar server.
+
+----  End Calendar Event Summary  ----
+
+
+--boundary
+Content-Type: text/calendar; charset=utf-8
+Content-Transfer-Encoding: 7bit
+
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;VALUE=DATE:20080601
+DTEND;VALUE=DATE:20080602
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DESCRIPTION:Testing iMIP from the calendar server.
+ORGANIZER;CN=User 02:mailto:user2 at example.com
+SUMMARY:This is an event
+END:VEVENT
+END:VCALENDAR
+
+--boundary--
+""",
+                    ),
+                   (
+                       """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;VALUE=DATE:20080601
+DTEND;VALUE=DATE:20080602
+RRULE:FREQ=YEARLY
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 03:mailto:user3 at example.com
+SUMMARY:This is an event
+DESCRIPTION:Testing iMIP from the calendar server.
+END:VEVENT
+END:VCALENDAR
+""",
+                        """From: ${fromaddress}
+To: ${toaddress}
+Subject: DO NOT REPLY: calendar invitation test
+Mime-Version: 1.0
+Content-Type: multipart/mixed;
+    boundary="boundary"
+
+
+--boundary
+Content-Type: text/plain
+
+Hi,
+You've been invited to a cool event by CalendarServer's new iMIP processor.
+
+---- Begin Calendar Event Summary ----
+
+Organizer:   User 03 <mailto:user3 at example.com>
+Summary:     This is an event
+Starts:      Sunday, June  1, 2008
+Ends:        Monday, June  2, 2008
+Duration:    1 day
+All Day
+Recurring
+Description: Testing iMIP from the calendar server.
+
+----  End Calendar Event Summary  ----
+
+
+--boundary
+Content-Type: text/calendar; charset=utf-8
+Content-Transfer-Encoding: 7bit
+
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;VALUE=DATE:20080601
+DTEND;VALUE=DATE:20080602
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DESCRIPTION:Testing iMIP from the calendar server.
+ORGANIZER;CN=User 03:mailto:user3 at example.com
+RRULE:FREQ=YEARLY
+SUMMARY:This is an event
+END:VEVENT
+END:VCALENDAR
+
+--boundary--
+""",
+                    ),
+                )
+        
+        def _normalizeMessage(text):
+            # First get rid of unwanted headers
+            lines = text.split("\n")
+            lines = [line for line in lines if line.split(":")[0] not in ("Date", "Message-ID",)]
+            
+            # Now get rid of boundary string
+            boundary = None
+            newlines = []
+            for line in lines:
+                if line.startswith("    boundary=\""):
+                    boundary = line[len("    boundary=\""):-1]
+                    line = line.replace(boundary, "boundary")
+                if boundary and line.find(boundary) != -1:
+                    line = line.replace(boundary, "boundary")
+                newlines.append(line)
+            return "\n".join(newlines)
+
+        for data, result in data:
+            imip = ServerToIMip(self.DummyScheduler(Component.fromString(data)), [], [])
+            self.assertEqual(_normalizeMessage(imip._generateTemplateMessage(imip.scheduler.calendar)), result)
+        

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080613/1c055f58/attachment-0001.htm 


More information about the calendarserver-changes mailing list