#####################################################################
#
# MaildropHost  An asynchronous MailHost replacement
#
# This software is governed by a license. See
# LICENSE.txt for the terms of this license.
#
#####################################################################
__version__ = "$Revision: 1312 $"[11:-2]

# General python imports
from email.Utils import make_msgid
from random import randint
from types import StringType
from types import UnicodeType
import os
import sys

# Zope imports
from Globals import DTMLFile
from Globals import InitializeClass
from Globals import package_home
from AccessControl import ClassSecurityInfo
from Products.MailHost.MailHost import MailHost

# BBB - Zope 2.8 and on use "transaction.get" instead of get_transaction
try:
    import transaction
    get_transaction = transaction.get
    begin_transaction = transaction.manager.begin
except ImportError:
    # Zope 2.7 and earlier. No need to do anything, get_transaction is 
    # stuffed into __builtins__ in those Zope versions.
    pass

# MaildropHost package imports
from config import MAILDROP_HOME
try:
    from config import MAILDROP_SPOOL
    MAILDROP_SPOOLS = [x.strip() for x in MAILDROP_SPOOL.split(';')]
except:
    MAILDROP_SPOOLS = [os.path.join(MAILDROP_HOME, 'spool')]

from config import MAILDROP_INTERVAL
from config import MAILDROP_TLS
from config import MAILDROP_LOGIN
from config import MAILDROP_PASSWORD
from config import SMTP_HOST
from config import SMTP_PORT
from config import DEBUG
from config import WAIT_INTERVAL

try:
    from config import DEBUG_RECEIVER
except ImportError:
    DEBUG_RECEIVER = ''

try:
    from config import ADD_MESSAGEID
except ImportError:
    ADD_MESSAGEID = 0

for spool in MAILDROP_SPOOLS:
    if not os.path.isdir(spool):
        os.makedirs(spool)

def makeTempPath():
    """ Helper to create a temp file name safely """
    spool = MAILDROP_SPOOLS[0]
    temp_path = os.path.join(spool, str(randint(100000, 9999999)))

    while os.path.exists(temp_path):
        temp_path = os.path.join(spool, str(randint(100000, 9999999)))

    return temp_path 


addMaildropHostForm=DTMLFile('dtml/addMaildropHost_form', globals())
def manage_addMaildropHost(self, id, title='Maildrop Host', REQUEST=None):
    """ add a MaildropHost into the system """
    mh = MaildropHost(id, title)
    self._setObject(id, mh)

    if REQUEST is not None:
        ret_url = '%s/%s/manage_main' % (self.absolute_url(), id)
        REQUEST['RESPONSE'].redirect(ret_url)


class MaildropHost(MailHost):
    """ A MaildropHost """
    security = ClassSecurityInfo()
    meta_type = 'Maildrop Host'
    manage = manage_main = DTMLFile('dtml/manageMaildropHost', globals())
    manage_main._setName('manage_main')

    # Variables from the configuration file set as simple class attributes
    # so they can be read just like the standard MailHost attributes
    smtp_host = SMTP_HOST
    smtp_port = SMTP_PORT
    debug = DEBUG and 'On' or 'Off'
    debug_receiver = DEBUG_RECEIVER or '(not set)'
    polling = MAILDROP_INTERVAL
    spool = MAILDROP_SPOOLS[0]
    use_tls = ( (MAILDROP_TLS > 1 and 'Forced') or 
                (MAILDROP_TLS == 1 and 'Yes') or 
                'No' )
    login = MAILDROP_LOGIN or '(not set)'
    password = MAILDROP_PASSWORD and '******' or '(not set)'
    add_messageid = ADD_MESSAGEID and 'On' or 'Off'

    manage_options = (
      ( { 'label' : 'Edit', 'action' : 'manage_main'
        , 'help' : ('MaildropHost', 'MaildropHost_Edit.stx')
        }
      ,
      )
      + MailHost.manage_options[1:]
      )
      


    def __init__(self, id, title='Maildrop Host'):
        """ Initialize a new MaildropHost """
        self.id = id
        self.title = title
        self._transactional = True


    security.declareProtected('Change configuration', 'manage_makeChanges')
    def manage_makeChanges( self
                          , title
                          , transactional=False
                          , REQUEST=None
                          , **ignored
                          ):
        """ Change the MaildropHost properties """
        self.title = title
        self._transactional = not not transactional

        if REQUEST is not None:
            msg = 'MaildropHost "%s" updated' % self.id
            return self.manage_main(manage_tabs_message=msg)


    security.declareProtected('Change configuration', 'isTransactional')
    def isTransactional(self):
        """ Is transactional mode in use? """
        return getattr(self, '_transactional', True)


    def _send(self, m_from, m_to, body):
        """ Send a mail using the asynchronous maildrop handler """
        if self.add_messageid == 'On':
            body = 'Message-Id: %s\n%s' % (make_msgid(), body)

        if self.isTransactional():
            email = TransactionalEmail(m_from, m_to, body)
        else:
            email = Email(m_from, m_to, body)

        return email.send()


class Email:
    """ Simple non-persistent class to model a email message """

    def __init__(self, mfrom, mto, body):
        """ Instantiate a new email object """
        if DEBUG_RECEIVER != '':
            mto = DEBUG_RECEIVER

        if not (isinstance(mto, StringType) or isinstance(mto, UnicodeType)):
            self.m_to = ','.join(mto).replace('\r', '').replace('\n', '')
        else:
            self.m_to = mto.replace('\r', '').replace('\n', '')
        self.m_from = mfrom.replace('\r', '').replace('\n', '')
        self.body = body
        self._tempfile = ''
        self._transactional = False


    def send(self):
        """ Write myself to the file system """
        temp_path = makeTempPath()
        self._tempfile = temp_path
        lock_path = '%s.lck' % temp_path

        lock = open(lock_path, 'w')
        lock.write('locked')
        lock.close()

        temp = open(temp_path, 'w')
        temp.write(MAIL_TEMPLATE % (self.m_to, self.m_from, self.body))
        temp.close()

        if not self._transactional:
            os.unlink(lock_path)
        else:
            self._lockfile = lock_path


class TransactionalEmail(Email):
    """ Transaction-aware email class """

    def __init__(self, mfrom, mto, body):
        """ Instantiate a new transactional email object """
        Email.__init__(self, mfrom, mto, body)
        self._lockfile = ''
        self._transaction_done = 0
        self._transactional = True

        get_transaction().register(self)


    #######################################################
    # ZODB Transaction hooks
    #######################################################

    def commit(self, reallyme, t): 
        """ Called for every (sub-)transaction commit """
        pass


    def tpc_begin(self, transaction): 
        """ Called at the beginning of a transaction """
        pass


    def tpc_abort(self, transaction): 
        """ Called on abort - but not always :( """
        pass


    def abort(self, reallyme, t): 
        """ Called if the transaction has been aborted """
        try:
            os.remove(self._lockfile)
        except:
            pass

        try:
            os.remove(self._tempfile)
        except (IOError, OSError):
            pass

        self._tempfile = ''
        self._lockfile = ''


    def tpc_vote(self, transaction):
        """ Only called for real transactions, not subtransactions """
        self._transaction_done = 1


    def tpc_finish(self, transaction):
        """ Called at the end of a successful transaction """
        if getattr(self, '_transaction_done', 0):
            try:
                os.remove(self._lockfile)
            finally:
                self._lockfile = ''
                self._tempfile = ''


    def sortKey(self, *ignored):
        """ The sortKey method is used for recent ZODB compatibility which
            needs to have a known commit order for lock acquisition.
            I don't care about commit order, so return the constant 1
        """
        return 1


MAIL_TEMPLATE = """##To:%s
##From:%s
%s
"""


InitializeClass(MaildropHost) 

