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

# General python imports
from types import StringType, UnicodeType
from random import randint
import os, sys

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

# MaildropHost package imports
from config import MAILDROP_HOME, SMTP_HOST, SMTP_PORT, POLLING, DEBUG


MAILDROP_SPOOL = os.path.join(MAILDROP_HOME, 'spool')
if not os.path.isdir(MAILDROP_SPOOL):
    os.makedirs(MAILDROP_SPOOL)

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

    while os.path.exists(temp_path):
        temp_path = os.path.join(MAILDROP_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
    polling = POLLING
    spool = MAILDROP_SPOOL

    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 = 0


    security.declareProtected('Change configuration', 'manage_makeChanges')
    def manage_makeChanges( self
                          , title
                          , transactional=0
                          , 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 self._transactional


    def _send(self, m_from, m_to, body):
        """ Send a mail using the asynchronous maildrop handler """
        if self._transactional:
            email = TransactionalEmail(m_from, m_to, body)
        else:
            email = Email(m_from, m_to, body)

        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 not isinstance(mto, StringType) or not isinstance(mto, UnicodeType):
            self.m_to = ','.join(mto)
        else:
            self.m_to = mto
        self.m_from = mfrom
        self.body = body


    def send(self):
        """ Write myself to the file system """
        temp_path = makeTempPath()
        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()

        os.unlink(lock_path)


class TransactionalEmail:
    """ Transaction-aware email class """

    def __init__(self, mfrom, mto, body):
        """ Instantiate a new transactional email object """
        if not isinstance(mto, StringType) or not isinstance(mto, UnicodeType):
            self.m_to = ','.join(mto)
        else:
            self.m_to = mto
        self.m_from = mfrom
        self.body = body
        self._tempfile = ''
        self._lockfile = ''
        self._transaction_done = 0

        get_transaction().register(self)


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

        lock = open(self._lockfile, '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()


    #######################################################
    # 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) 

