VirtualMailManager/Alias.py
author Pascal Volk <neverseen@users.sourceforge.net>
Fri, 26 Feb 2010 02:35:25 +0000
branchv0.6.x
changeset 216 0c8c053b451c
parent 215 33f727efa7c4
child 221 371ae0b4443d
permissions -rw-r--r--
Moved VirtualMailManager/Exceptions to VirtualMailManager/errors. Renamed VMM*Exception classes to *Error. No longer add the attribute 'message' to VMMError if it doesn't exist, like in Python 2.4. It has been deprecated as of Python 2.6. Also removed the methods code() and msg(), the values are now accessible via the attributes 'code' and 'msg'.

# -*- coding: UTF-8 -*-
# Copyright (c) 2007 - 2010, Pascal Volk
# See COPYING for distribution information.

"""
    VirtualMailManager.Alias

    Virtual Mail Manager's Alias class to manage e-mail aliases.
"""

from VirtualMailManager.Domain import get_gid
from VirtualMailManager.EmailAddress import EmailAddress
from VirtualMailManager.errors import AliasError as AErr
from VirtualMailManager.constants.ERROR import ALIAS_ADDR_DEST_IDENTICAL, \
     ALIAS_EXCEEDS_EXPANSION_LIMIT, ALIAS_EXISTS, NO_SUCH_ALIAS


_ = lambda msg: msg


class Alias(object):
    """Class to manage e-mail aliases."""
    __slots__ = ('_addr', '_dests', '_gid', '_dbh')

    def __init__(self, dbh, address):
        assert isinstance(address, EmailAddress)
        self._addr = address
        self._dbh = dbh
        self._gid = get_gid(self._dbh, self._addr.domainname)
        self._dests = []

        self.__load_dests()

    def __load_dests(self):
        """Loads all known destination addresses into the _dests list."""
        dbc = self._dbh.cursor()
        dbc.execute(
                'SELECT destination FROM alias WHERE gid=%s AND address=%s',
                    self._gid, self._addr.localpart)
        dests = iter(dbc.fetchall())
        if dbc.rowcount > 0:
            dest_add = self._dests.append
            for dest in dests:
                dest_add(EmailAddress(dest[0]))
        dbc.close()

    def __check_expansion(self, limit):
        """Checks the current expansion limit of the alias."""
        dcount = len(self._dests)
        failed = False
        if dcount == limit:
            failed = True
            errmsg = _(
u"""Can't add new destination to alias %(address)r.
Currently this alias expands into %(count)i/%(limit)i recipients.
One more destination will render this alias unusable.
Hint: Increase Postfix' virtual_alias_expansion_limit""")
        elif dcount > limit:
            failed = True
            errmsg = _(
u"""Can't add new destination to alias %(address)r.
This alias already exceeds it's expansion limit (%(count)i/%(limit)i).
So its unusable, all messages addressed to this alias will be bounced.
Hint: Delete some destination addresses.""")
        if failed:
            raise AErr(errmsg % {'address': str(self._addr), 'count': dcount,
                                 'limit': limit},
                       ALIAS_EXCEEDS_EXPANSION_LIMIT)

    def __delete(self, destination=None):
        """Deletes a destination from the alias, if ``destination`` is
        not ``None``.  If ``destination`` is None, the alias with all
        it's destination addresses will be deleted.

        """
        dbc = self._dbh.cursor()
        if not destination:
            dbc.execute("DELETE FROM alias WHERE gid=%s AND address=%s",
                        self._gid, self._addr.localpart)
        else:
            dbc.execute("DELETE FROM alias WHERE gid=%s AND address=%s AND \
 destination=%s",
                        self._gid, self._addr.localpart, str(destination))
        if dbc.rowcount > 0:
            self._dbh.commit()
        dbc.close()

    def __len__(self):
        """Returns the number of destinations of the alias."""
        return len(self._dests)

    def add_destination(self, destination, expansion_limit):
        """Adds the ``destination`` `EmailAddress` to the alias."""
        assert isinstance(destination, EmailAddress)
        if self._addr == destination:
            raise AErr(_(u"Address and destination are identical."),
                       ALIAS_ADDR_DEST_IDENTICAL)
        if destination in self._dests:
            raise AErr(_(
                u'The alias %(a)r has already the destination %(d)r.') %
                       {'a': str(self._addr), 'd': str(destination)},
                       ALIAS_EXISTS)
        self.__check_expansion(expansion_limit)
        dbc = self._dbh.cursor()
        dbc.execute('INSERT INTO alias (gid, address, destination) \
VALUES (%s, %s, %s)',
                    self._gid, self._addr.localpart, str(destination))
        self._dbh.commit()
        dbc.close()
        self._dests.append(destination)

    def del_destination(self, destination):
        """Deletes the specified ``destination`` address from the alias."""
        assert isinstance(destination, EmailAddress)
        if not self._dests:
            raise AErr(_(u"The alias %r doesn't exist.") % str(self._addr),
                       NO_SUCH_ALIAS)
        if not destination in self._dests:
            raise AErr(_(u"The address %(d)r isn't a destination of \
the alias %(a)r.") %
                       {'a': str(self._addr), 'd': str(destination)},
                       NO_SUCH_ALIAS)
        self.__delete(destination)
        self._dests.remove(destination)

    def get_destinations(self):
        """Returns an iterator for all destinations of the alias."""
        if not self._dests:
            raise AErr(_(u"The alias %r doesn't exist.") % str(self._addr),
                       NO_SUCH_ALIAS)
        return iter(self._dests)

    def delete(self):
        """Deletes the alias with all it's destinations."""
        if not self._dests:
            raise AErr(_(u"The alias %r doesn't exist.") % str(self._addr),
                       NO_SUCH_ALIAS)
        self.__delete()
        del self._dests[:]


del _