diff --git a/Mailnag/backends/base.py b/Mailnag/backends/base.py index 2a90ad1..0a55ecd 100644 --- a/Mailnag/backends/base.py +++ b/Mailnag/backends/base.py @@ -67,6 +67,11 @@ def request_folders(self): """ raise NotImplementedError + def supports_notifications(self): + """Returns True if mailbox supports notifications.""" + # Default implementation + return False + @abstractmethod def notify_next_change(self, callback=None, timeout=None): """Asks mailbox to notify next change. diff --git a/Mailnag/backends/imap.py b/Mailnag/backends/imap.py index dacecc2..2bd0875 100644 --- a/Mailnag/backends/imap.py +++ b/Mailnag/backends/imap.py @@ -40,7 +40,7 @@ class IMAPMailboxBackend(MailboxBackend): """Implementation of IMAP mail boxes.""" def __init__(self, name = '', user = '', password = '', oauth2string = '', - server = '', port = '', ssl = True, folders = [], **kw): + server = '', port = '', ssl = True, folders = [], idle=True, **kw): self.name = name self.user = user self.password = password @@ -49,6 +49,7 @@ def __init__(self, name = '', user = '', password = '', oauth2string = '', self.port = port self.ssl = ssl # bool self.folders = [encode_mutf7(folder) for folder in folders] + self.idle = idle self._conn = None @@ -130,6 +131,11 @@ def request_folders(self): return lst + def supports_notifications(self): + """Returns True if mailbox supports notifications. + IMAP mailbox supports notifications if idle parameter is True""" + return self.idle + def notify_next_change(self, callback=None, timeout=None): self._ensure_open() diff --git a/Mailnag/common/accounts.py b/Mailnag/common/accounts.py index 7cfc584..54ba9fe 100644 --- a/Mailnag/common/accounts.py +++ b/Mailnag/common/accounts.py @@ -116,6 +116,11 @@ def list_messages(self): return self._get_backend().list_messages() + def supports_notifications(self): + """Returns True if account supports notifications.""" + return self._get_backend().supports_notifications() + + def notify_next_change(self, callback=None, timeout=None): """Asks mailbox to notify next change. Callback is called when new mail arrives or removed. diff --git a/Mailnag/daemon/idlers.py b/Mailnag/daemon/idlers.py index 8bbb413..b3e740a 100644 --- a/Mailnag/daemon/idlers.py +++ b/Mailnag/daemon/idlers.py @@ -4,7 +4,7 @@ # idlers.py # # Copyright 2011 - 2016 Patrick Ulbrich -# Copyright 2016 Timo Kankare +# Copyright 2016, 2018 Timo Kankare # Copyright 2011 Leighton Earl # # This program is free software; you can redistribute it and/or modify @@ -136,7 +136,7 @@ def __init__(self, accounts, sync_callback, idle_timeout): def start(self): for acc in self._accounts: - if acc.imap and acc.idle: + if acc.supports_notifications(): try: idler = Idler(acc, self._sync_callback, self._idle_timeout) idler.start() diff --git a/Mailnag/daemon/mailnagdaemon.py b/Mailnag/daemon/mailnagdaemon.py index 58a3f82..c0fe8f6 100644 --- a/Mailnag/daemon/mailnagdaemon.py +++ b/Mailnag/daemon/mailnagdaemon.py @@ -150,8 +150,7 @@ def check_for_mails(self): # have been closed already. self._ensure_valid_state() - non_idle_accounts = filter(lambda acc: (not acc.imap) or - (acc.imap and not acc.idle), self._accounts) + non_idle_accounts = self._get_non_idle_accounts(self._accounts) self._mailchecker.check(non_idle_accounts) @@ -180,9 +179,8 @@ def _start(self): except: logging.exception('Caught an exception.') - idle_accounts = filter(lambda acc: acc.imap and acc.idle, self._accounts) - non_idle_accounts = filter(lambda acc: (not acc.imap) or - (acc.imap and not acc.idle), self._accounts) + idle_accounts = self._get_idle_accounts(self._accounts) + non_idle_accounts = self._get_non_idle_accounts(self._accounts) # start polling thread for POP3 accounts and # IMAP accounts without idle support @@ -232,8 +230,16 @@ def _wait_for_inet_connection(self): # (see timeout in mailnag.cleanup()) # ..but also don't sleep to short in case of a ping connection test. time.sleep(3) - - + + + def _get_idle_accounts(self, accounts): + return [acc for acc in self._accounts if acc.supports_notifications()] + + + def _get_non_idle_accounts(self, accounts): + return [acc for acc in self._accounts if not acc.supports_notifications()] + + def _load_plugins(self, cfg, hookreg, memorizer): class MailnagController_Impl(MailnagController): def __init__(self, daemon, memorizer, hookreg, shutdown_request_hdlr): diff --git a/Mailnag/daemon/mails.py b/Mailnag/daemon/mails.py index 4f18ae3..18e6213 100644 --- a/Mailnag/daemon/mails.py +++ b/Mailnag/daemon/mails.py @@ -85,8 +85,9 @@ def collect_mail(self, sort = True): sender, id, acc)) mail_ids[id] = None - # don't close idle connections - if not acc.idle: + # leave account with notifications open, so that it can + # send notifications about new mails + if not acc.supports_notifications(): # disconnect from Email-Server acc.close() diff --git a/tests/test_account.py b/tests/test_account.py index 4caa0cd..243a7af 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -22,6 +22,8 @@ """Test cases for Account.""" +import pytest + from Mailnag.common.accounts import Account @@ -128,3 +130,18 @@ def test_account_should_configurable_with_any_parameters(): assert config['weird'] == 'odd' assert config['odd'] == 'weird' + +@pytest.mark.parametrize("config,should_support", [ + ({'mailbox_type': 'imap', 'idle': True}, True), + ({'mailbox_type': 'imap', 'idle': False}, False), + ({'mailbox_type': 'pop3'}, False), + ({'mailbox_type': 'mbox'}, False), + ({'mailbox_type': 'maildir'}, False), +]) +def test_account_supports_notifications(config, should_support): + account = Account(**config) + if should_support: + assert account.supports_notifications() + else: + assert not account.supports_notifications() +