[CalendarServer-changes] [2753] CalendarServer/branches/users/sagen/mailgateway-implicit-2745/ twistedcaldav/mail.py
source_changes at macosforge.org
source_changes at macosforge.org
Tue Jul 22 14:12:46 PDT 2008
Revision: 2753
http://trac.macosforge.org/projects/calendarserver/changeset/2753
Author: sagen at apple.com
Date: 2008-07-22 14:12:46 -0700 (Tue, 22 Jul 2008)
Log Message:
-----------
Checkpoint
Modified Paths:
--------------
CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/mail.py
Modified: CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/mail.py
===================================================================
--- CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/mail.py 2008-07-22 18:51:59 UTC (rev 2752)
+++ CalendarServer/branches/users/sagen/mailgateway-implicit-2745/twistedcaldav/mail.py 2008-07-22 21:12:46 UTC (rev 2753)
@@ -20,7 +20,7 @@
"""
from twisted.internet import protocol, defer, ssl
-from twisted.web.client import HTTPClientFactory
+from twisted.web import resource, static, server, client
from twisted.internet.defer import fail, succeed, inlineCallbacks, returnValue
from twisted.protocols import basic
from twisted.mail import pop3client, imap4
@@ -33,6 +33,7 @@
from twistedcaldav.resource import CalDAVResource
from twistedcaldav.scheduling.scheduler import IMIPScheduler
from twistedcaldav.config import config, parseConfig, defaultConfig
+from twistedcaldav.sql import AbstractSQLDatabase
from zope.interface import Interface, implements
import email, uuid
@@ -227,7 +228,7 @@
scheme = "https:" if useSSL else "http:"
url = "%s//%s:%d/%s/" % (scheme, host, port, path)
- factory = HTTPClientFactory(url, method='POST', headers=headers,
+ factory = client.HTTPClientFactory(url, method='POST', headers=headers,
postdata=data)
if useSSL:
reactor.connectSSL(host, port, factory, ssl.ClientContextFactory())
@@ -341,10 +342,60 @@
client = namedClass(settings["Service"])(settings)
client.setServiceParent(multiService)
+ IScheduleService().setServiceParent(multiService)
+
return multiService
#
+# ISchedule Inbox
+#
+class IScheduleService(service.Service, LoggingMixIn):
+
+ def __init__(self): # TODO: settings
+ root = resource.Resource()
+ root.putChild('', self.HomePage())
+ root.putChild('inbox', self.IScheduleInbox())
+ self.site = server.Site(root)
+ self.server = internet.TCPServer(62311, self.site)
+
+ def startService(self):
+ self.server.startService()
+
+ def stopService(self):
+ self.server.stopService()
+
+
+ class HomePage(resource.Resource):
+ def render(self, request):
+ return """
+ <html>
+ <head><title>ISchedule - IMIP Gateway</title></head>
+ <body>ISchedule - IMIP Gateway</body>
+ </html>
+ """
+
+ class IScheduleInbox(resource.Resource):
+
+ def render_GET(self, request):
+ return """
+ <html>
+ <head><title>ISchedule Inbox</title></head>
+ <body>ISchedule Inbox</body>
+ </html>
+ """
+
+ def render_POST(self, request):
+ return """
+ <html>
+ <head><title>ISchedule Inbox</title></head>
+ <body>POSTED ISchedule Inbox</body>
+ </html>
+ """
+
+
+
+#
# POP3
#
@@ -607,3 +658,230 @@
self.connector = connector
self.log_info("IMAP factory connection failed")
self.retry(connector)
+
+
+
+
+"""
+class ScheduleViaIMip(DeliveryService):
+
+ @classmethod
+ def serviceType(cls):
+ return DeliveryService.serviceType_imip
+
+ @inlineCallbacks
+ def generateSchedulingResponses(self):
+
+ # Generate an HTTP client request
+ try:
+ # We do not do freebusy requests via iMIP
+ if self.freebusy:
+ raise ValueError("iMIP VFREEBUSY REQUESTs not supported.")
+
+ # Copy the ical component because we need to substitute the
+ # calendar server's sepcial email address for ORGANIZER
+ itip = self.scheduler.calendar.duplicate( )
+ itip.getOrganizerProperty().setValue("mailto:SERVER_ADDRESS+TOKEN at HOST.NAME")
+ # TODO: generate +address token
+ # TODO: grab server email address from config
+
+ message = self._generateTemplateMessage(itip)
+
+ # The email's From: will be the calendar server's address (without
+ # + addressing), while the Reply-To: will be the organizer's email
+ # address.
+ cuAddr = self.scheduler.originator.cuaddr
+ if not cuAddr.startswith("mailto:"):
+ raise ValueError("ORGANIZER address '%s' must be mailto: for iMIP operation." % (fromAddr,))
+ cuAddr = cuAddr[7:]
+ fromAddr = "SERVER_ADDRESS at HOST.NAME"
+ message = message.replace("${fromaddress}", fromAddr)
+ message = message.replace("${replytoaddress}", cuAddr)
+
+ 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)
+ log.debug("Sending iMIP message To: '%s', From :'%s'\n%s" % (toAddr, fromAddr, sendit,))
+ yield sendmail(config.Scheduling[self.serviceType()]["Sending"]["Server"], fromAddr, toAddr, sendit, port=config.Scheduling[self.serviceType()]["Sending"]["Port"])
+
+ 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-imip 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("Reply-To", "${replytoaddress}")
+ 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
+"""
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080722/0b57ddd8/attachment-0001.html
More information about the calendarserver-changes
mailing list