[Zope3-checkins] CVS: Zope3/src/zope/app/mail - configure.zcml:1.1.14.1 event.py:1.1.14.1 meta.zcml:1.1.14.1 metaconfigure.py:1.2.2.1 service.py:1.2.2.1 mail.py:NONE mailer.py:NONE

Viktorija Zaksiene ryzaja@codeworks.lt
Thu, 22 May 2003 04:52:11 -0400


Update of /cvs-repository/Zope3/src/zope/app/mail
In directory cvs.zope.org:/tmp/cvs-serv25415/src/zope/app/mail

Modified Files:
      Tag: cw-mail-branch
	configure.zcml event.py meta.zcml metaconfigure.py service.py 
Removed Files:
      Tag: cw-mail-branch
	mail.py mailer.py 
Log Message:
Marius and Viktorija.
Refactored Mail delivery service. The work is still not finished, so we
commit it to the branch.


=== Zope3/src/zope/app/mail/configure.zcml 1.1 => 1.1.14.1 ===
--- Zope3/src/zope/app/mail/configure.zcml:1.1	Wed Apr 16 09:45:43 2003
+++ Zope3/src/zope/app/mail/configure.zcml	Thu May 22 04:51:40 2003
@@ -1,21 +1,8 @@
 <zopeConfigure
    xmlns="http://namespaces.zope.org/zope"
-   xmlns:service="http://namespaces.zope.org/service"
-   xmlns:mail="http://namespaces.zope.org/mail"
    >
 
-<serviceType id="Mail" 
+<serviceType id="Mail"
 	     interface="zope.app.interfaces.mail.IMailService" />
-
-<mail:mailservice name="Mail"
-    hostname="localhost" port="25"
-    class=".service.AsyncMailService" 
-    permission="zope.Public"/>
-
-<mail:mailer name="SimpleMailer" class=".mailer.SimpleMailer" 
-             serviceType="Mail" default="True" /> 
-
-<mail:mailer name="BatchMailer" class=".mailer.BatchMailer" 
-             serviceType="Mail" /> 
 
 </zopeConfigure>


=== Zope3/src/zope/app/mail/event.py 1.1 => 1.1.14.1 ===
--- Zope3/src/zope/app/mail/event.py:1.1	Wed Apr 16 09:45:43 2003
+++ Zope3/src/zope/app/mail/event.py	Thu May 22 04:51:40 2003
@@ -15,13 +15,28 @@
 
 $Id$
 """
+from zope.interface import implements
 from zope.app.interfaces.mail import IMailSentEvent
+from zope.app.interfaces.mail import IMailErrorEvent
+
+__metaclass__ = type
 
 
 class MailSentEvent:
     __doc__ = IMailSentEvent.__doc__
 
-    __implements__ =  IMailSentEvent
+    implements(IMailSentEvent)
+
+    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
 
-    def __init__(self, mailer):
-        self.mailer = mailer 


=== Zope3/src/zope/app/mail/meta.zcml 1.1 => 1.1.14.1 ===
--- Zope3/src/zope/app/mail/meta.zcml:1.1	Wed Apr 16 09:45:43 2003
+++ Zope3/src/zope/app/mail/meta.zcml	Thu May 22 04:51:40 2003
@@ -1,12 +1,12 @@
 <zopeConfigure xmlns="http://namespaces.zope.org/zope">
-  
+
   <directives namespace="http://namespaces.zope.org/mail">
 
-    <directive name="mailservice" handler=".metaconfigure.mailservice">
+    <directive name="queuedService" handler=".metaconfigure.queuedService">
 
       <description>
-        This directive creates and registers a global mail service. It should
-        be only called once during startup. 
+	This directive creates and registers a global queued mail service. It
+	should be only called once during startup. 
       </description>
 
       <attribute name="name" required="no">
@@ -22,77 +22,88 @@
         </description>
       </attribute>
 
-      <attribute name="class" required="yes">
+      <attribute name="queuePath" required="yes">
         <description>
-	  Class of the Mail Service.
+	  Defines the path for the queue directory.
         </description>
       </attribute>
 
-      <attribute name="hostname" required="no">
-        <description>
-	  Name of the server that is used to send the mail. Default is set to
-	  'localhost'.
-        </description>
-      </attribute>
+    </directive>
 
-      <attribute name="port" required="no">
+    <directive name="directService" handler=".metaconfigure.directService">
+
+      <description>
+	This directive creates and registers a global direct mail service. It
+	should be only called once during startup. 
+      </description>
+
+      <attribute name="name" required="no">
         <description>
-	  Port on the server that is used to send the mail. Default is set to
-	  to the standard port '25'.
+	  Specifies the Service name of the mail service. The default is
+          "Mail".
         </description>
       </attribute>
 
-      <attribute name="username" required="no">
+      <attribute name="permission" required="yes">
         <description>
-	  Some SMTP servers support authentication. If no username is given,
-          then the Mail Service will not try to use authentication.
+	  Defines the permission that is required to use this object.
         </description>
       </attribute>
 
-      <attribute name="password" required="no">
+      <attribute name="mailer" required="yes">
         <description>
-	  Password that is used for authentication. Makes only sense in
-	  combination with username. 
+	  Defines the mailer to be used for sending mail.
         </description>
       </attribute>
 
     </directive>
 
-    <directive name="mailer" handler=".metaconfigure.mailer">
+    <!-- example of a mailer directive
+
+    <directive name="smtp" handler=".metaconfigure.smtp">
 
       <description>
-        Registers a new mailer class wiht the global translation service.
+	Registers a new SMTP mailer.
       </description>
 
-      <attribute name="name" required="yes">
+      <attribute name="id" required="yes">
+	<description>
+	  ID of the mailer.
+        </description>
+      </attribute>
+
+      <attribute name="hostname" required="no">
         <description>
-	  Name of the mailer class under which it is registered in the global
-          mail service.
+	  Name of the server that is used to send the mail. Default is set to
+	  'localhost'.
         </description>
       </attribute>
 
-      <attribute name="class" required="yes">
+      <attribute name="port" required="no">
         <description>
-	  The class representing this object.
+	  Port on the server that is used to send the mail. Default is set to
+	  to the standard port '25'.
         </description>
       </attribute>
 
-      <attribute name="serviceType" required="no">
+      <attribute name="username" required="no">
         <description>
-	  Specifies the service type for which the mailer should be
-          registered. The default is "Mail".
+	  Some SMTP servers support authentication. If no username is given,
+          then the Mail Service will not try to use authentication.
         </description>
       </attribute>
 
-      <attribute name="default" required="no">
+      <attribute name="password" required="no">
         <description>
-	  Specifies whether this mailer is the default mailer. The default
-	  value for the 'default' attribute is "False".
+	  Password that is used for authentication. Makes only sense in
+	  combination with username. 
         </description>
       </attribute>
 
     </directive>
 
+    -->
+
   </directives>
 
-</zopeConfigure>
\ No newline at end of file
+</zopeConfigure>


=== Zope3/src/zope/app/mail/metaconfigure.py 1.2 => 1.2.2.1 ===
--- Zope3/src/zope/app/mail/metaconfigure.py:1.2	Mon May 19 06:03:37 2003
+++ Zope3/src/zope/app/mail/metaconfigure.py	Thu May 22 04:51:40 2003
@@ -18,17 +18,15 @@
 from zope.component import getService
 from zope.configuration.action import Action
 from zope.app.component.metaconfigure import provideService
+from zope.app.mail.service import QueuedMailService, DirectMailService
 
 
-def mailservice(_context, class_, permission, name="Mail",
-                hostname="localhost", port=25, username=None, password=None):
-
-    component = _context.resolve(class_)()
-    component.hostname = hostname
-    component.port = int(port)
-    component.username = username
-    component.password = password
 
+def queuedService(_context, permission, queuePath, name="Mail"):
+    # XXX what if queuePath is relative?  I'd like to make it absolute here,
+    # but should it be relative to $CWD or $INSTANCE_HOME (if there is one
+    # in Zope 3)?
+    component = QueuedMailService(queuePath)
     return [
         Action(
             discriminator = ('service', name),
@@ -37,22 +35,42 @@
             )
         ]
 
-
-def mailer(_context, name, class_, serviceType="Mail", default=False):
-    klass = _context.resolve(class_)
-
-    if default == "True":
-        default = True
-
-    def register(serviceType, name, klass, default):
-        mailservice = getService(None, serviceType)
-        mailservice.provideMailer(name, klass, default)
-
-
+def directService(_context, permission, mailer, name="Mail"):
+    mailer_component = queryMailer(mailer)
+    if mailer_component is None:
+        raise ConfigurationError("Mailer %r is not defined" % mailer)
+    component = DirectMailService(mailer_component)
     return [
         Action(
-             discriminator = ('mailer', name),
-             callable = register,
-             args = (serviceType, name, klass, default)
-             )
+            discriminator = ('service', name),
+            callable = provideService,
+            args = (name, component, permission),
+            )
         ]
+
+# Example of mailer configuration:
+#
+#   def smtp(_context, id, hostname, port):
+#       component = SMTPMailer(hostname, port)
+#       if queryMailer(id) is not None:
+#           raise ConfigurationError("Redefinition of mailer %r" % id)
+#       provideMailer(id, component)
+#       return []
+#
+# or is it better to make mailer registration an Action?  But that won't work,
+# because queryMailer will get called during directive processing, before any
+# actions are run.
+
+
+mailerRegistry = {}
+queryMailer = mailerRegistry.get
+provideMailer = mailerRegistry.__setitem__
+
+# Register our cleanup with Testing.CleanUp to make writing unit tests simpler.
+try:
+    from zope.testing.cleanup import addCleanUp
+except ImportError:
+    pass
+else:
+    addCleanUp(mailerRegistry.clear)
+    del addCleanUp


=== Zope3/src/zope/app/mail/service.py 1.2 => 1.2.2.1 ===
--- Zope3/src/zope/app/mail/service.py:1.2	Mon May 19 06:03:37 2003
+++ Zope3/src/zope/app/mail/service.py	Thu May 22 04:51:40 2003
@@ -11,58 +11,96 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""MailService Implementation
-
-This module contains various implementations of MailServices.
+"""Mail service implementation
 
 $Id$
 """
-from zope.app.interfaces.mail import IAsyncMailService
+import rfc822
+from cStringIO import StringIO
+from random import randrange
+from time import strftime
+from socket import gethostname
+from os import getpid
+from zope.interface import implements
+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)
 
-class AsyncMailService:
-    __doc__ = IAsyncMailService.__doc__
+    def __init__(self, queuePath):
+        self._queuePath = queuePath
 
-    __implements__ = IAsyncMailService
+    queuePath = property(lambda self: self._queuePath)
 
-    # 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
+    def createDataManager(self, fromaddr, toaddrs, message):
+        maildir = Maildir(self.queuePath, True)
+        msg = maildir.newMessage()
+        msg.write(message)
+        return MailDataManager(msg.commit, onAbort=msg.abort)

=== Removed File Zope3/src/zope/app/mail/mail.py ===

=== Removed File Zope3/src/zope/app/mail/mailer.py ===