[Zope3-checkins] CVS: Zope3/src/zope/app/mail - event.py:1.1.14.5 mailer.py:1.3.2.6 service.py:1.2.2.6
Albertas Agejevas
alga@codeworks.lt
Mon, 23 Jun 2003 11:00:49 -0400
Update of /cvs-repository/Zope3/src/zope/app/mail
In directory cvs.zope.org:/tmp/cvs-serv26043/src/zope/app/mail
Modified Files:
Tag: cw-mail-branch
event.py mailer.py service.py
Log Message:
Bummer. Wiped out my changes with a HEAD merge. Here they are back.
=== Zope3/src/zope/app/mail/event.py 1.1.14.4 => 1.1.14.5 ===
--- Zope3/src/zope/app/mail/event.py:1.1.14.4 Mon Jun 23 10:20:12 2003
+++ Zope3/src/zope/app/mail/event.py Mon Jun 23 11:00:49 2003
@@ -15,7 +15,9 @@
$Id$
"""
-from zope.app.interfaces.mail import IMailSentEvent
+from zope.interface import implements
+from zope.app.interfaces.mail import IMailSentEvent, IMailErrorEvent
+__metaclass__ = type
from zope.interface import implements
@@ -24,5 +26,15 @@
implements(IMailSentEvent)
- def __init__(self, mailer):
- self.mailer = mailer
+ def __init__(self, messageId):
+ self.messageId = messageId
+
+
+class MailErrorEvent:
+ __doc__ = IMailErrorEvent.__doc__
+
+ implements(IMailErrorEvent)
+
+ def __init__(self, messageId, errorMessage):
+ self.messageId = messageId
+ self.errorMessage = errorMessage
=== Zope3/src/zope/app/mail/mailer.py 1.3.2.5 => 1.3.2.6 ===
--- Zope3/src/zope/app/mail/mailer.py:1.3.2.5 Mon Jun 23 10:20:12 2003
+++ Zope3/src/zope/app/mail/mailer.py Mon Jun 23 11:00:49 2003
@@ -11,62 +11,52 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""MailService Implementation
-Simple implementation of the MailService, Mailers and MailEvents.
+"""These are classes which abstract different channels an email
+message could be sent out by.
$Id$
"""
-from smtplib import SMTP
-from zope.app.interfaces.mail import IMailer, IBatchMailer
-from zope.app.event import publish
-from zope.app.mail.event import MailSentEvent
from zope.interface import implements
+from zope.app.interfaces.mail import ISendmailMailer, ISMTPMailer
+from os import popen
+from smtplib import SMTP
+
+__metaclass__ = type
+
+class SendmailMailer:
+
+ implements(ISendmailMailer)
+
+ # A hook for unit tests
+ popen = popen
+
+ def __init__(self, command="/usr/lib/sendmail -oem -oi -f %(from)s %(to)s"):
+ self.command = command
+
+ def send(self, fromaddr, toaddrs, message):
+ command = self.command % {'from': fromaddr, 'to': " ".join(toaddrs)}
+ f = self.popen(command, "w")
+ f.write(message)
+ f.close()
+
+class SMTPMailer:
+ implements(ISMTPMailer)
-class SimpleMailer:
- __doc__ = IMailer.__doc__
+ smtp = SMTP
- implements(IMailer)
+ def __init__(self, hostname='localhost', port=25,
+ username=None, password=None):
+ self.hostname = hostname
+ self.port = port
+ self.username = username
+ self.password = password
- def send(self, fromaddr, toaddrs, message,
- hostname, port, username, password):
- "See zope.app.interfaces.services.mail.IMailer"
- server = SMTP(hostname, port)
- server.set_debuglevel(0)
- if username is not None and password is not None:
- server.login(username, password)
- server.sendmail(fromaddr, toaddrs, message)
- server.quit()
- publish(self, MailSentEvent(self))
-
-
-class BatchMailer:
- __doc__ = IBatchMailer.__doc__
-
- implements(IBatchMailer)
-
- # See zope.app.interfaces.mail.IBatchMailer
- batchDelay = 5000
-
- # See zope.app.interfaces.mail.IBatchMailer
- batchSize = 5
-
- def send(self, fromaddr, toaddrs, message,
- hostname, port, username, password):
- "See zope.app.interfaces.mail.IMailer"
- server = SMTP(hostname, port)
- server.set_debuglevel(0)
- if username is not None and password is not None:
- server.login(username, password)
- recv = list(toaddrs)
- batch = []
- while recv:
- while len(batch) < self.batchSize and recv:
- batch.append(recv.pop())
- server.sendmail(fromaddr, batch, message)
- batch = []
- time.sleep(self.batchDelay/1000.0)
- server.quit()
- publish(self, MailSentEvent(self))
+ def send(self, fromaddr, toaddrs, message):
+ connection = self.smtp(self.hostname, str(self.port))
+ if self.username is not None and self.password is not None:
+ connection.login(self.username, self.password)
+ connection.sendmail(fromaddr, toaddrs, message)
+ connection.quit()
=== Zope3/src/zope/app/mail/service.py 1.2.2.5 => 1.2.2.6 ===
--- Zope3/src/zope/app/mail/service.py:1.2.2.5 Mon Jun 23 10:20:12 2003
+++ Zope3/src/zope/app/mail/service.py Mon Jun 23 11:00:49 2003
@@ -11,59 +11,176 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""MailService Implementation
+"""Mail service implementation
This module contains various implementations of MailServices.
$Id$
"""
-from zope.app.interfaces.mail import IAsyncMailService
+import rfc822
+import threading
+import os.path
+import logging
+from os import listdir, unlink
+from cStringIO import StringIO
+from random import randrange
+from time import strftime
+from socket import gethostname
+from os import getpid
+from time import sleep
from zope.interface import implements
-
-class AsyncMailService:
- __doc__ = IAsyncMailService.__doc__
-
- implements(IAsyncMailService)
-
- # See zope.app.interfaces.services.mail.IMailService
- hostname = u''
-
- # See zope.app.interfaces.services.mail.IMailService
- port = 25
-
- # See zope.app.interfaces.services.mail.IMailService
- username = None
-
- # See zope.app.interfaces.services.mail.IMailService
- password = None
-
- def __init__(self):
- """Initialize the object."""
- self.__mailers = {}
- self.__default_mailer = ''
-
- def createMailer(self, name):
- "See zope.app.interfaces.services.mail.IAsyncMailService"
- return self.__mailers[name]()
-
- def getMailerNames(self):
- "See zope.app.interfaces.services.mail.IAsyncMailService"
- return self.__mailers.keys()
-
- def getDefaultMailerName(self):
- "See zope.app.interfaces.services.mail.IAsyncMailService"
- return self.__default_mailer
-
- def send(self, fromaddr, toaddrs, message, mailer=None):
- "See zope.app.interfaces.services.mail.IMailService"
- if mailer is None:
- mailer = self.createMailer(self.getDefaultMailerName())
- # XXX: should be called in new thread:should we use thread or async?
- mailer.send(fromaddr, toaddrs, message, self.hostname, self.port,
- self.username, self.password)
-
- def provideMailer(self, name, klass, default=False):
- """Add a new mailer to the service."""
- self.__mailers[name] = klass
- if default:
- self.__default_mailer = name
+from zope.app.interfaces.mail import IDirectMailService, IQueuedMailService
+from zope.app.mail.maildir import Maildir
+from transaction.interfaces import IDataManager
+from transaction import get_transaction
+
+__metaclass__ = type
+
+class MailDataManager:
+ """XXX I need a docstring"""
+
+ implements(IDataManager)
+
+ def __init__(self, callable, args=(), onAbort=None):
+ self.callable = callable
+ self.args = args
+ self.onAbort = onAbort
+
+ def prepare(self, transaction):
+ pass
+
+ def abort(self, transaction):
+ if self.onAbort:
+ self.onAbort()
+
+ def commit(self, transaction):
+ self.callable(*self.args)
+
+ def savepoint(self, transaction):
+ pass
+
+
+class AbstractMailService:
+
+ def newMessageId(self):
+ """Generates a new message ID according to RFC 2822 rules"""
+ randmax = 0x7fffffff
+ left_part = '%s.%d.%d' % (strftime('%Y%m%d%H%M%S'),
+ getpid(),
+ randrange(0, randmax))
+ return "%s@%s" % (left_part, gethostname())
+
+ def send(self, fromaddr, toaddrs, message):
+ parser = rfc822.Message(StringIO(message))
+ messageid = parser.getheader('Message-Id')
+ if messageid:
+ if not messageid.startswith('<') or not messageid.endswith('>'):
+ raise ValueError('Malformed Message-Id header')
+ messageid = messageid[1:-1]
+ else:
+ messageid = self.newMessageId()
+ message = 'Message-Id: <%s>\n%s' % (messageid, message)
+ get_transaction().join(self.createDataManager(fromaddr, toaddrs, message))
+ return messageid
+
+
+class DirectMailService(AbstractMailService):
+ __doc__ = IDirectMailService.__doc__
+
+ implements(IDirectMailService)
+
+ def __init__(self, mailer):
+ self.mailer = mailer
+
+ def createDataManager(self, fromaddr, toaddrs, message):
+ return MailDataManager(self.mailer.send, args=(fromaddr, toaddrs, message))
+
+
+class QueuedMailService(AbstractMailService):
+ __doc__ = IQueuedMailService.__doc__
+
+ implements(IQueuedMailService)
+
+ def __init__(self, queuePath):
+ self._queuePath = queuePath
+
+ queuePath = property(lambda self: self._queuePath)
+
+ def createDataManager(self, fromaddr, toaddrs, message):
+ maildir = Maildir(self.queuePath, True)
+ msg = maildir.newMessage()
+ msg.write('X-Zope-From: %s\n' % fromaddr)
+ msg.write('X-Zope-To: %s\n' % ", ".join(toaddrs))
+ msg.write(message)
+ return MailDataManager(msg.commit, onAbort=msg.abort)
+
+class QueueProcessorThread(threading.Thread):
+ """This thread is started at configuration time from the
+ mail:queuedService directive handler.
+ """
+ log = logging.getLogger("QueueProcessorThread")
+
+ def setMaildir(self, maildir):
+ """Set the maildir.
+
+ This method is used just to provide a maildir stubs ."""
+ self.maildir = maildir
+
+ def setQueuePath(self, path):
+ self.maildir = Maildir(path)
+
+ def setMailer(self, mailer):
+ self.mailer = mailer
+
+ def _parseMessage(self, message):
+ """Extract fromaddr and toaddrs from the first two lines of
+ the message.
+
+ Returns a fromaddr string, a toaddrs tuple and the message
+ string.
+ """
+
+ fromaddr = ""
+ toaddrs = ()
+ rest = ""
+
+ try:
+ first, second, rest = message.split('\n', 2)
+ except ValueError:
+ return fromaddr, toaddrs, message
+
+ if first.startswith("X-Zope-From: "):
+ i = len("X-Zope-From: ")
+ fromaddr = first[i:]
+
+ if second.startswith("X-Zope-To: "):
+ i = len("X-Zope-To: ")
+ toaddrs = tuple(second[i:].split(", "))
+
+ return fromaddr, toaddrs, rest
+
+ def run(self, forever=True):
+ while True:
+ for filename in self.maildir:
+ try:
+ file = open(filename)
+ message = file.read()
+ file.close()
+ fromaddr, toaddrs, message = self._parseMessage(message)
+ self.mailer.send(fromaddr, toaddrs, message)
+ unlink(filename)
+ # XXX maybe log the Message-Id of the message sent
+ self.log.info("Mail from %s to %s sent.",
+ fromaddr, ", ".join(toaddrs))
+ # Blanket except because we don't want this thread to ever die
+ except:
+ # XXX maybe throw away erroring messages here?
+ self.log.error("Error while sending mail from %s to %s.",
+ fromaddr, ", ".join(toaddrs), exc_info=1)
+ else:
+ if forever:
+ sleep(3)
+
+ # A testing plug
+ if not forever:
+ break