#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Unity Mail, the main application class
# Authors: Dmitry Shachnev <mitya57@gmail.com>
#          Robert Tari <robert.tari@gmail.com>
# License: GNU GPL 3 or higher; http://www.gnu.org/licenses/gpl.html

APPVERSION = '1.7.5'

import gi

gi.require_version('Gtk', '3.0')
gi.require_version('Notify', '0.7')

import email
import email.errors
import email.header
import email.utils
import dbus
import dbus.service
import logging
import secretstorage
import subprocess
import sys
import time
import re
import UnityMail.imaplib2 as imaplib
import os.path
import os
from gi.repository import GLib, Gtk, Notify
from socket import error as socketerror
from dbus.mainloop.glib import DBusGMainLoop
from UnityMail import g_oTranslation, MessagingMenu, g_oSettings, openURLOrCommand, g_dctDefaultURLs
from UnityMail.idler import Idler
from UnityMail.config import ConfigFile, config_file_path
from UnityMail.dialog import PreferencesDialog, MESSAGEACTION
from UnityMail.actions import DialogActions

imaplib._MAXLINE = 500000#160000 # See discussion in LP: #1309566
logger = logging.getLogger('Unity Mail')
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(name)s: %(levelname)s: %(message)s'))
logger.addHandler(handler)
logger.propagate = False
m_reThrid = re.compile(b'THRID (\\d+)')
fixFormat = lambda string: string.replace('%(t0)s', '{t0}').replace('%(t1)s', '{t1}')

def decodeWrapper(header):

    # Decodes an Email header, returns a string
    # A hack for headers without a space between decoded name and email
    try:

        dec = email.header.decode_header(header.replace('=?=<', '=?= <'))

    except email.errors.HeaderParseError:

        logger.warning('Exception in decode, skipping')
        return header

    parts = []

    for dec_part in dec:

        if dec_part[1]:

            try:
                parts.append(dec_part[0].decode(dec_part[1]))
            except (AttributeError, LookupError, UnicodeDecodeError):
                logger.warning('Exception in decode, skipping')

        elif isinstance(dec_part[0], bytes):

            parts.append(dec_part[0].decode())

        else:
            parts.append(dec_part[0])

    return str.join(' ', parts)

def getHeaderWrapper(message, header_name, decode):

    header = message[header_name]

    if isinstance(header, str):

        header = header.replace(' \r\n', '').replace('\r\n', '')
        return (decodeWrapper(header) if decode else header)

    return ''

def getSenderName(sender):

    # Strips address, and returns only name
    sname = email.utils.parseaddr(sender)[0]

    return sname if sname else sender

class SessionBus(dbus.service.Object):

    fnSettings = None
    fnClear = None
    fnIsInit = None

    def __init__(self, fnSettings, fnClear, fnIsInit):

        oBusName = dbus.service.BusName('in.tari.unitymail', bus=dbus.SessionBus())
        dbus.service.Object.__init__(self, oBusName, '/in/tari/unitymail')

        self.fnSettings = fnSettings
        self.fnClear = fnClear
        self.fnIsInit = fnIsInit

    @dbus.service.method('in.tari.unitymail')
    def settings(self):
        self.fnSettings()

    @dbus.service.method('in.tari.unitymail')
    def clear(self):
        self.fnClear()

    @dbus.service.method('in.tari.unitymail')
    def isinit(self):
        return self.fnIsInit()

class Connection(object):

    def __init__(self, host, port, login, passwd, folder):

        self.strHost = host
        self.nPort = port
        self.strLogin = login
        self.strPasswd = passwd
        self.oImap = None
        self.lstNotificationQueue = []
        self.oIdler = None
        self.strFolder = folder

class Message(object):

    def __init__(self, oConnection, strUId, title, message_id, timestamp, strSender, thread_id=None):

        self.oConnection = oConnection
        self.strUId = strUId
        self.title = title
        self.message_id = message_id
        self.timestamp = timestamp
        self.thread_id = thread_id
        self.strSender = strSender

class UnityMail(object):

    def __init__(self):

        self.dlgSettings = None
        self.nLastMailTimestamp = 0
        self.first_run = True
        self.lstAccounts = []
        self.lstConnections = []
        self.lstUnreadMessages = []
        self.bDrawAttention = False
        self.bIdlerRunning = False
        self.bReconnecting = False
        self.oMessagingMenu = MessagingMenu(self.onMenuItemClicked, self.openDialog, self.updateMessageAges)

        self.initKeyring()
        self.initConfig()
        DBusGMainLoop(set_as_default=True)
        SessionBus(self.openDialog, self.clear, lambda: bool(self.lstAccounts))
        Notify.init('Unity Mail')
        GLib.set_application_name('Unity Mail')

        if not self.bIdlerRunning:

            GLib.timeout_add_seconds(5, self.check, self.lstConnections)
            self.bIdlerRunning = True

        try:
            GLib.MainLoop().run()
        except KeyboardInterrupt:
            self.close(0)

    def clear(self):

        # WARNING: loadDataFromDicts also calls this!
        if self.lstAccounts:

            for oMessage in self.lstUnreadMessages:

                #self.markMessageAsRead(message)
                self.oMessagingMenu.remove(oMessage.message_id)

            self.setLauncherCount(0)

    def closeConnections(self):

        for oConnection in self.lstConnections:

            if oConnection.oIdler:

                oConnection.oIdler.stop()
                oConnection.oIdler.join()
                oConnection.oIdler = None

            if oConnection.oImap:

                try:
                    oConnection.oImap.close()
                except imaplib.IMAP4.error as oError:
                    pass

                oConnection.oImap.logout()
                oConnection.oImap = None

        self.lstConnections = []
        self.bIdlerRunning = False

    def close(self, nCode):

        self.oMessagingMenu.close()
        self.closeConnections()
        print()
        sys.exit(nCode)

    def onMenuItemClicked(self, strId):

        for message in self.lstUnreadMessages:

            if message.message_id == strId:

                if self.nMessageAction == MESSAGEACTION['MARK']:

                    self.markMessageAsRead(message)

                elif self.nMessageAction == MESSAGEACTION['ASK']:

                    dlg = DialogActions(message.strSender, message.title)
                    nResponse = dlg.run()
                    dlg.destroy()

                    if nResponse == 100:

                        try:

                            if any(s in message.oConnection.strHost for s in ['gmail', 'google']):

                                message.oConnection.oImap.uid('STORE', message.strUId, '+X-GM-LABELS', '\\Trash')

                            else:

                                message.oConnection.oImap.uid('STORE', message.strUId, '+FLAGS', '\\Deleted')
                                message.oConnection.oImap.expunge()

                        except (imaplib.IMAP4.error, socketerror) as oError:

                            logger.error(str(oError))

                    elif nResponse == 200:

                        self.markMessageAsRead(message)

                    elif nResponse == 300:

                        openURLOrCommand('Inbox', message.thread_id)

                    else:

                        self.bDrawAttention = not message.timestamp < self.nLastMailTimestamp
                        self.appendToIndicator(message)
                        self.bDrawAttention = False
                        return False

                else:

                    openURLOrCommand('Inbox', message.thread_id)

        # True removes the message from Appindicator3
        return True

    def markMessageAsRead(self, message):

        # Mark entire conversation
        lstIndexes = [message.strUId]

        if message.thread_id and self.bMergeConversation:

            lstSearch = []

            try:

                lstSearch = message.oConnection.oImap.uid('SEARCH', None, '(X-GM-THRID ' + str(int(message.thread_id, 16)) + ')')

            except imaplib.IMAP4.error as oError:

                logger.error(str(oError))
                return

            if lstSearch[1][0] is not None:
                lstIndexes = [m for m in lstSearch[1][0].split()]

        for strIndex in lstIndexes:

            try:
                message.oConnection.oImap.uid('STORE', strIndex, '+FLAGS', '\\Seen')
            except (imaplib.IMAP4.error, socketerror) as e:
                logger.error(str(e))

    def initConfig(self):

        # REMOVE THIS BLOCK IN 1.8
        if os.path.isfile(config_file_path):

            config = ConfigFile(config_file_path)
            self.nMaxCount = config.get_int_with_default('MaxItemCount', 20)
            self.bEnableNotifications = config.get_bool_with_default('EnableNotifications', True)
            self.bPlaySound = config.get_bool_with_default('EnableSound', True)
            self.bHideCount = config.get_bool_with_default('HideMessagesCount', True)
            self.strCommand = config.get_str_with_default('ExecOnReceive')
            self.custom_sound = config.get_str_with_default('CustomSound')
            self.bMergeConversation = config.get_bool_with_default('MergeMessages', False)
            self.nMessageAction = config.get_int_with_default('MessageAction', MESSAGEACTION['OPEN'])
            self.on_click_urls = {}

            for urlname in 'Home', 'Compose', 'Inbox', 'Sent':
                self.on_click_urls[urlname] = config.get_str_with_default(urlname, g_dctDefaultURLs[urlname], section = 'URLs')

            g_oSettings.set_int('max-item-count', self.nMaxCount)
            g_oSettings.set_boolean('enable-notifications', self.bEnableNotifications)
            g_oSettings.set_boolean('enable-sound', self.bPlaySound)
            g_oSettings.set_boolean('hide-messages-count', self.bHideCount)
            g_oSettings.set_string('exec-on-receive', self.strCommand)
            g_oSettings.set_string('custom-sound', self.custom_sound)
            g_oSettings.set_boolean('merge-messages', self.bMergeConversation)
            g_oSettings.set_enum('message-action', self.nMessageAction)
            g_oSettings.set_string('home', self.on_click_urls['Home'])
            g_oSettings.set_string('compose', self.on_click_urls['Compose'])
            g_oSettings.set_string('inbox', self.on_click_urls['Inbox'])
            g_oSettings.set_string('sent', self.on_click_urls['Sent'])

            os.remove(config_file_path)

        else:

            self.nMaxCount = g_oSettings.get_int('max-item-count')
            self.bEnableNotifications = g_oSettings.get_boolean('enable-notifications')
            self.bPlaySound = g_oSettings.get_boolean('enable-sound')
            self.bHideCount = g_oSettings.get_boolean('hide-messages-count')
            self.strCommand = g_oSettings.get_string('exec-on-receive')
            self.custom_sound = g_oSettings.get_string('custom-sound')
            self.bMergeConversation = g_oSettings.get_boolean('merge-messages')
            self.nMessageAction = g_oSettings.get_enum('message-action')
            self.on_click_urls = {}
            self.on_click_urls['Home'] = g_oSettings.get_string('home')
            self.on_click_urls['Compose'] = g_oSettings.get_string('compose')
            self.on_click_urls['Inbox'] = g_oSettings.get_string('inbox')
            self.on_click_urls['Sent'] = g_oSettings.get_string('sent')

    def initKeyring(self):

        bus = secretstorage.dbus_init()

        try:

            self.collection = secretstorage.get_default_collection(bus)
            self.collection.is_locked()

        except secretstorage.SecretStorageException as e:

            logger.critical(str(e))
            self.close(1)

        if self.collection.is_locked():
            self.collection.unlock()

        if self.collection.is_locked():

            logger.critical('Failed to unlock the collection, exiting')
            self.close(1)

        self.mail_keys = list(self.collection.search_items({'application': 'unity-mail'}))

        if not self.mail_keys:
            self.openDialog()

        if not self.lstAccounts:

            for key in sorted(self.mail_keys, key=lambda item: item.item_path):

                dctAttributes = key.get_attributes()
                strHost = dctAttributes['server']
                nPort = int(dctAttributes['port'])
                strLogin = dctAttributes['username']
                strPasswd = key.get_secret().decode('utf-8')
                strFolders = 'INBOX'

                try:
                    strFolders = dctAttributes['folders']
                except KeyError:
                    pass

                self.lstAccounts.append({'Host': strHost, 'Port': nPort, 'Login': strLogin, 'Passwd': strPasswd, 'Folders': strFolders})

                for strFolder in strFolders.split('\t'):
                    self.lstConnections.append(Connection(strHost, nPort, strLogin, strPasswd, strFolder))

    def createKeyringItem(self, ind, update=False):

        strFolders = 'INBOX'

        try:
            strFolders = self.lstAccounts[ind]['Folders']
        except KeyError:
            pass

        attrs = {'application': 'unity-mail', 'service': 'imap', 'server': self.lstAccounts[ind]['Host'], 'port': str(self.lstAccounts[ind]['Port']), 'username': self.lstAccounts[ind]['Login'], 'folders': strFolders}
        label = 'unity-mail: ' + self.lstAccounts[ind]['Login'] + ' at ' + self.lstAccounts[ind]['Host']

        if update:

            self.mail_keys[ind].set_attributes(attrs)
            self.mail_keys[ind].set_secret(self.lstAccounts[ind]['Passwd'])
            self.mail_keys[ind].set_label(label)

        else:

            self.collection.unlock()
            self.collection.create_item(label, attrs, self.lstAccounts[ind]['Passwd'], True)

    def openDialog(self):

        if not self.dlgSettings:

            self.dlgSettings = PreferencesDialog()
            self.dlgSettings.connect('response', self.onDialogResponse)

            if self.mail_keys:
                self.dlgSettings.setAccounts(self.lstAccounts)

            self.dlgSettings.run()
            self.dlgSettings.destroy()
            self.dlgSettings = None

    def onDialogResponse(self, dlg, response):

        if response == Gtk.ResponseType.APPLY:

            dlg.updateAccounts()
            dlg.saveAllSettings()

            self.lstAccounts = dlg.lstDicts

            self.loadDataFromDicts()

            for index in range(len(self.lstAccounts), len(self.mail_keys)):

                # Remove old keys
                self.mail_keys[index].delete()

            for index in range(len(self.lstAccounts)):

                # Create new keys or update existing
                self.createKeyringItem(index, update=(index < len(self.mail_keys)))

            self.mail_keys = list(self.collection.search_items({'application': 'unity-mail'}))

            if self.mail_keys and self.lstAccounts and self.lstConnections and not self.bIdlerRunning:

                self.bIdlerRunning = True
                GLib.timeout_add_seconds(5, self.check, self.lstConnections)

        if response in [Gtk.ResponseType.APPLY, Gtk.ResponseType.CANCEL]:
            dlg.destroy()

    def loadDataFromDicts(self):

        self.closeConnections()
        self.initConfig()
        self.clear()
        self.lstUnreadMessages = []
        self.nLastMailTimestamp = 0
        self.first_run = True

        for dct in self.lstAccounts:

            strFolders = 'INBOX'

            try:
                strFolders = dct['Folders']
            except KeyError:
                pass

            nPort = 993

            try:
                nPort = int(dct['Port'])
            except ValueError:
                pass

            for strFolder in strFolders.split('\t'):
                self.lstConnections.append(Connection(dct['Host'], nPort, dct['Login'], dct['Passwd'], strFolder))

    def establishConnection(self, oConnection):

        try:

            oConnection.oImap = imaplib.IMAP4_SSL(oConnection.strHost, oConnection.nPort)

        except Exception:

            oConnection.oImap = imaplib.IMAP4(oConnection.strHost, oConnection.nPort)
            oConnection.oImap.starttls()

    def tryEstablishConnection(self, oConnection):

        try:
            self.establishConnection(oConnection)
        except Exception:
            return False

        try:

            oConnection.oImap.login(oConnection.strLogin, oConnection.strPasswd)

        except (imaplib.IMAP4.error, UnicodeEncodeError) as e:

            oNotification = Notify.Notification.new(_('Invalid account data'), '', 'unity-mail')
            oNotification.set_property('body', _('Unable to connect to account "{accountName}", the application will now exit.').format(accountName=oConnection.strLogin) + '\n\n' + _('You can run "{command}" to delete all your login settings.').format(command='unity-mail-reset'))
            oNotification.set_hint('desktop-entry', GLib.Variant.new_string('unity-mail'))
            oNotification.set_timeout(Notify.EXPIRES_NEVER)
            oNotification.show()
            self.close(1)

        return True

    def appendToIndicator(self, message):

        if self.oMessagingMenu.hasSource(message.message_id):
            return

        title = message.title

        if len(title) > 50:
            title = title[:50] + '...'

        if len(self.lstAccounts) > 1:
            title = '↪ ' + message.oConnection.strLogin + '\n' + title

        self.oMessagingMenu.append(message.message_id, title, message.timestamp, self.bDrawAttention)

    def encodeFolderName(self, folder):

        folder_name_utf7 = bytes(folder, 'utf-7').replace(b'+', b'&').replace(b' &', b'- &')

        if b' ' in folder_name_utf7:
            folder_name_utf7 = b'"' + folder_name_utf7 + b'"'

        return folder_name_utf7

    def onIdle(self, oConnection):

        lstMessages = []
        lstNewMessages = []
        lstUnread = []

        try:

            search = oConnection.oImap.uid('SEARCH', '(UNSEEN)')

        except imaplib.IMAP4.abort as oError:

            if not self.bReconnecting:

                logger.info('Connection to "{0}:{1}" closed. Trying to reconnect.'.format(oConnection.strLogin, oConnection.strFolder))
                GLib.timeout_add_seconds(5, self.check, [oConnection])
                self.bReconnecting = True

            return

        if search[1][0] is not None:
            lstMessages = search[1][0].split()

        for m in lstMessages[-self.nMaxCount:]:

            typ = None
            msg_data = None
            thread_id = None
            msg = None

            try:

                typ, msg_data = oConnection.oImap.uid('FETCH', m, '(X-GM-THRID BODY.PEEK[HEADER.FIELDS (DATE SUBJECT FROM MESSAGE-ID)])')

                for lstField in msg_data:

                    if 'THRID' in str(lstField):

                        thread_id = '%x' % int(m_reThrid.search(lstField[0]).group(1))
                        break

            except imaplib.IMAP4.error:

                typ, msg_data = oConnection.oImap.uid('FETCH', m, '(BODY.PEEK[HEADER.FIELDS (DATE SUBJECT FROM MESSAGE-ID)])')
                thread_id = None

            for response_part in msg_data:

                if isinstance(response_part, tuple):
                    msg = email.message_from_bytes(response_part[1])

            if msg is None:
                continue

            message_id = msg['Message-Id']

            if not isinstance(message_id, str):
                message_id = oConnection.strHost + ':' + oConnection.strLogin + ':' + oConnection.strFolder + ':' + str(m.decode())

            bMessageExists = False

            for cMessage in self.lstUnreadMessages:

                if cMessage.message_id == message_id:

                    bMessageExists = True
                    break

            if not bMessageExists:

                sender = getHeaderWrapper(msg, 'From', True)
                subj = getHeaderWrapper(msg, 'Subject', True)
                date = getHeaderWrapper(msg, 'Date', False)

                try:

                    tuple_time = email.utils.parsedate_tz(date)
                    timestamp = email.utils.mktime_tz(tuple_time)

                    if timestamp > time.time():

                        # Message time is larger than the current one
                        timestamp = time.time()

                except TypeError:

                    # Failed to get time from message
                    timestamp = time.time()

                # Number of seconds to number of microseconds
                timestamp *= (10**6)

                while subj.lower().startswith('re:'):
                    subj = subj[3:]

                while subj.lower().startswith('fwd:'):
                    subj = subj[4:]

                subj = subj.strip()

                if sender.startswith('"'):

                    pos = sender[1:].find('"')

                    if pos >= 0:
                        sender = sender[1:pos+1]+sender[pos+2:]

                ilabel = subj if subj else _('No subject')

                # Display only last message in thread
                bConversationInUnread = False
                bConversationInNew = False

                if thread_id and self.bMergeConversation:

                    for oMessage in self.lstUnreadMessages:

                        if oMessage.thread_id == thread_id:

                            oMessage.timestamp = max(timestamp, oMessage.timestamp)
                            bConversationInUnread = True
                            break

                    if not bConversationInUnread:

                        for oMessage in lstNewMessages:

                            if oMessage.thread_id == thread_id:

                                oMessage.timestamp = max(timestamp, oMessage.timestamp)
                                bConversationInNew = True
                                break

                if not bConversationInUnread and not bConversationInNew:

                    message = Message(oConnection, m, ilabel, message_id, timestamp, sender, thread_id)
                    lstNewMessages.append(message)

                if timestamp > self.nLastMailTimestamp:

                    if self.bEnableNotifications:

                        if not bConversationInNew:

                            oConnection.lstNotificationQueue.append([sender, subj, oConnection, thread_id])

                        else:

                            for lstNotification in oConnection.lstNotificationQueue:

                                if lstNotification[3] == thread_id:

                                    lstNotification[0] = sender
                                    break

                    self.nLastMailTimestamp = timestamp
                    self.bDrawAttention = True

                if self.strCommand and not self.first_run:

                    try:

                        subprocess.call((self.strCommand, sender, ilabel))

                    except OSError as e:

                        # File doesn't exist or is not executable
                        logger.warning('Cannot execute command: {0}'.format(str(e)))

            else:

                lstUnread.append(message_id)

        self.updateIndicator(lstNewMessages, [oMessage for oMessage in self.lstUnreadMessages if oMessage.oConnection == oConnection and oMessage.message_id not in lstUnread], oConnection)

    def updateIndicator(self, lstNewMessages, lstRemovedMessages, oConnection):

        for oMessage in [oMessage for oMessage in self.lstUnreadMessages if oMessage.oConnection == oConnection]:

            # Removed outside the app
            if oMessage in lstRemovedMessages:
                self.oMessagingMenu.remove(oMessage.message_id)

            # Cleared
            if not self.oMessagingMenu.hasSource(oMessage.message_id) and oMessage not in lstNewMessages:

                self.markMessageAsRead(oMessage)
                self.lstUnreadMessages.remove(oMessage)

        self.lstUnreadMessages = sorted(self.lstUnreadMessages + lstNewMessages, key=lambda m: m.timestamp)[-self.nMaxCount:]

        #logger.debug('Unread: {0}, New: {1}, Removed: {2}'.format(len(self.lstUnreadMessages), len(lstNewMessages), len(lstRemovedMessages)))

        if lstNewMessages:

            for cMessage in lstNewMessages:
                self.appendToIndicator(cMessage)

        try:

            if self.first_run and oConnection != self.lstConnections[-1]:

                pass

            elif self.first_run and oConnection == self.lstConnections[-1]:

                self.showNotifications()
                self.first_run = False

            elif lstNewMessages:

                self.showNotifications()

        except GLib.GError as e:

            logger.warning(str(e))

        self.setLauncherCount(len(self.lstUnreadMessages))

        if not self.first_run:
            self.bDrawAttention = False

    def updateMessageAges(self):

        for oMessage in self.lstUnreadMessages:
            self.oMessagingMenu.update(oMessage.message_id, oMessage.timestamp)

        return True

    def check(self, lstConnections):

        for oConnection in lstConnections:

            if oConnection.oIdler:

                oConnection.oIdler.stop()
                oConnection.oIdler.join()
                oConnection.oIdler = None

            if oConnection.oImap:

                try:
                    oConnection.oImap.close()
                except imaplib.IMAP4.error as oError:
                    pass

                oConnection.oImap.logout()
                oConnection.oImap = None

            try:

                if not self.tryEstablishConnection(oConnection):
                    continue

                if oConnection.oImap.select(mailbox=self.encodeFolderName(oConnection.strFolder))[0] != 'OK':

                    logger.error('Mailbox "{0}:{1}" does not exist'.format(oConnection.strLogin, oConnection.strFolder))

                    if oConnection.oImap:

                        try:
                            oConnection.oImap.close()
                        except imaplib.IMAP4.error:
                            pass

                        oConnection.oImap.logout()
                        oConnection.oImap = None

                    continue

                logger.info('Connection to "{0}:{1}" established'.format(oConnection.strLogin, oConnection.strFolder))
                self.onIdle(oConnection)
                oConnection.oIdler = Idler(oConnection, self.onIdle)
                oConnection.oIdler.start()

            except (imaplib.IMAP4.error, socketerror) as e:

                logger.error('Error in connection "{0}:{1}": {2}'.format(oConnection.strLogin, oConnection.strFolder, str(e)))
                oConnection.oImap = None

            except KeyboardInterrupt:

                oConnection.oImap = None
                self.close(0)

        self.bReconnecting = False

        return False

    def setLauncherCount(self, nCount):

        self.oMessagingMenu.setCount(nCount, any([oImap for oImap in self.lstConnections]))

    def showNotifications(self):

        lstNotificationsQueue = []

        for oConnection in self.lstConnections:

            lstNotificationsQueue += oConnection.lstNotificationQueue
            oConnection.lstNotificationQueue = []

        number_of_mails = len(lstNotificationsQueue)
        basemessage = g_oTranslation.ngettext('You have %d unread mail', 'You have %d unread mails', number_of_mails)
        basemessage = basemessage.replace('%d', '{0}')

        if number_of_mails and self.bPlaySound:

            try:

                if self.custom_sound:
                    subprocess.call(('canberra-gtk-play', '-f', self.custom_sound))
                else:
                    subprocess.call(('canberra-gtk-play', '-i', 'message-new-email'))

            except OSError as e:

                logger.warning(str(e))

        if number_of_mails > 1:

            senders = set(getSenderName(lstNotification[0]) for lstNotification in lstNotificationsQueue)
            unknown_sender = ('' in senders)

            if unknown_sender:
                senders.remove('')

            ts = tuple(senders)

            if len(ts) > 2 or (len(ts) == 2 and unknown_sender):
                message = fixFormat(_('from %(t0)s, %(t1)s and others')).format(t0=ts[0], t1=ts[1])
            elif len(ts) == 2 and not unknown_sender:
                message = fixFormat(_('from %(t0)s and %(t1)s')).format(t0=ts[0], t1=ts[1])
            elif len(ts) == 1 and not unknown_sender:
                message = _('from %s').replace('%s', '{0}').format(getSenderName(ts[0]))
            else:
                message = None

            oNotification = Notify.Notification.new(basemessage.format(number_of_mails), message, 'unity-mail')
            oNotification.set_hint('desktop-entry', GLib.Variant.new_string('unity-mail'))
            oNotification.show()

        elif number_of_mails:

            lstNotification = lstNotificationsQueue[0]

            if lstNotification[0]:
                message = _('New mail from %s').replace('%s', '{0}').format(getSenderName(lstNotification[0]))
            else:
                message = basemessage.format(1)

            oNotification = Notify.Notification.new(message, lstNotification[1], 'unity-mail')
            oNotification.set_hint('desktop-entry', GLib.Variant.new_string('unity-mail'))
            oNotification.show()
