From f63193a98ad9365df5ae0d734c6eb70c2638f743 Mon Sep 17 00:00:00 2001 From: Test User Date: Fri, 2 Oct 2020 21:24:10 +0200 Subject: [PATCH 1/2] Adds plugin that allows marking IMAP messages as read --- Mailnag/backends/imap.py | 11 ++--- Mailnag/daemon/dbus.py | 2 +- Mailnag/daemon/mailchecker.py | 3 +- Mailnag/daemon/mails.py | 9 ++-- Mailnag/plugins/markasreadplugin.py | 76 +++++++++++++++++++++++++++++ 5 files changed, 89 insertions(+), 12 deletions(-) create mode 100644 Mailnag/plugins/markasreadplugin.py diff --git a/Mailnag/backends/imap.py b/Mailnag/backends/imap.py index ddb0a25..deab175 100644 --- a/Mailnag/backends/imap.py +++ b/Mailnag/backends/imap.py @@ -82,9 +82,9 @@ class IMAPMailboxBackend(MailboxBackend): for folder in folder_list: # select IMAP folder - conn.select(f'"{folder}"', readonly = True) + conn.select(f'"{folder}"') try: - status, data = conn.search(None, 'UNSEEN') # ALL or UNSEEN + status, data = conn.uid('SEARCH', None, '(UNSEEN)') # ALL or UNSEEN except: logging.warning('Folder %s does not exist.', folder) continue @@ -93,7 +93,7 @@ class IMAPMailboxBackend(MailboxBackend): logging.debug('Folder %s in status %s | Data: %s', (folder, status, data)) continue # Bugfix LP-735071 for num in data[0].split(): - typ, msg_data = conn.fetch(num, '(BODY.PEEK[HEADER])') # header only (without setting READ flag) + typ, msg_data = conn.uid('FETCH', num, '(BODY.PEEK[HEADER])') # header only (without setting READ flag) for response_part in msg_data: if isinstance(response_part, tuple): try: @@ -101,7 +101,7 @@ class IMAPMailboxBackend(MailboxBackend): except: logging.debug("Couldn't get IMAP message.") continue - yield (folder, msg) + yield (folder, msg, num.decode("utf-8")) def request_folders(self): @@ -225,8 +225,7 @@ class IMAPMailboxBackend(MailboxBackend): folder = self.folders[0] else: folder = "INBOX" - - conn.select(f'"{folder}"', readonly = True) + conn.select(f'"{folder}"') def _ensure_open(self): if not self.is_open(): diff --git a/Mailnag/daemon/dbus.py b/Mailnag/daemon/dbus.py index c8d01ac..e420adb 100644 --- a/Mailnag/daemon/dbus.py +++ b/Mailnag/daemon/dbus.py @@ -110,7 +110,7 @@ class DBusService(dbus.service.Object): d['sender_addr'] = addr # string (s) d['account_name'] = m.account_name # string (s) d['id'] = m.id # string (s) - + d['strID'] = m.strID # string (s) converted_mails.append(d) return converted_mails diff --git a/Mailnag/daemon/mailchecker.py b/Mailnag/daemon/mailchecker.py index 6df9de0..5a2bd0f 100644 --- a/Mailnag/daemon/mailchecker.py +++ b/Mailnag/daemon/mailchecker.py @@ -36,6 +36,7 @@ class MailChecker: self._conntest = conntest self._dbus_service = dbus_service self._count_on_last_check = 0 + self._all_mails = [] def check(self, accounts): @@ -65,7 +66,7 @@ class MailChecker: else: # mail is fetched the first time unseen_mails.append(mail) new_mails.append(mail) - + self._all_mails = all_mails self._memorizer.sync(all_mails) self._memorizer.save() self._firstcheck = False diff --git a/Mailnag/daemon/mails.py b/Mailnag/daemon/mails.py index 594c01f..c144547 100644 --- a/Mailnag/daemon/mails.py +++ b/Mailnag/daemon/mails.py @@ -35,13 +35,15 @@ from Mailnag.common.config import cfg_folder # Mail class # class Mail: - def __init__(self, datetime, subject, sender, id, account): + def __init__(self, datetime, subject, sender, id, account, strID): self.datetime = datetime self.subject = subject self.sender = sender + self.account = account self.account_name = account.name self.account_id = account.get_id() self.id = id + self.strID = strID # @@ -66,7 +68,7 @@ class MailCollector: logging.error("Failed to open mailbox for account '%s' (%s)." % (acc.name, ex)) continue - for folder, msg in acc.list_messages(): + for folder, msg, num in acc.list_messages(): sender, subject, datetime, msgid = self._get_header(msg) id = self._get_id(msgid, acc, folder, sender, subject, datetime) @@ -78,7 +80,7 @@ class MailCollector: # Also filter duplicates caused by Gmail labels. if id not in mail_ids: mail_list.append(Mail(datetime, subject, \ - sender, id, acc)) + sender, id, acc, num)) mail_ids[id] = None # leave account with notifications open, so that it can @@ -193,7 +195,6 @@ class MailSyncer: # collect mails from given accounts rcv_lst = MailCollector(self._cfg, accounts).collect_mail(sort = False) - # group received mails by account tmp = {} for acc in accounts: diff --git a/Mailnag/plugins/markasreadplugin.py b/Mailnag/plugins/markasreadplugin.py new file mode 100644 index 0000000..12d8fac --- /dev/null +++ b/Mailnag/plugins/markasreadplugin.py @@ -0,0 +1,76 @@ +# Copyright 2013 - 2020 Andreas Angerer +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# + +import os +import dbus +import threading +from Mailnag.common.plugins import Plugin +from Mailnag.common.i18n import _ + + +class MarkAsReadPlugin(Plugin): + def _mark_mail_as_read(self, mail_id): + mails = self.controller._mailchecker._all_mails + found = False + for mail in mails: + if mail_id == mail.id: + found = True + break + if not found: + self.controller._ensure_not_disposed() + self.controller._memorizer.set_to_seen(mail_id) + self.controller._memorizer.save() + return + backend = mail.account._get_backend() + mailid = mail.strID + conn = backend._conn + if type(backend).__name__ == 'IMAPMailboxBackend': + status, res = conn.uid("STORE", mailid, "+FLAGS", "(\Seen)") + self.controller._ensure_not_disposed() + self.controller._memorizer.set_to_seen(mail_id) + self.controller._memorizer.save() + + + + def _mark_mail_as_read_bak(self, mail_id, mail): + self.controller._ensure_not_disposed() + self.controller._memorizer.set_to_seen(mail_id) + self.controller._memorizer.save() + + def enable(self): + self.controller = self.get_mailnag_controller() + self.controller.mark_mail_as_read = self._mark_mail_as_read + + + def disable(self): + self.controller.mark_mail_as_read = self._mark_mail_as_read_bak + + + def get_manifest(self): + return (_("MarkAsReadPlugin"), + _("Marks mails as read on the IMAP server upon clicking them away."), + "1.0", + "Andreas Angerer") + + + def get_default_config(self): + return {} + + + def has_config_ui(self): + return False From 50d04b3c7cd85b4472620be9becc146ba17ca193 Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 5 Oct 2020 20:22:07 +0200 Subject: [PATCH 2/2] Implementating IMAP read feature directly in the daemon --- Mailnag/common/config.py | 1 + Mailnag/daemon/mailnagdaemon.py | 19 ++++++-- Mailnag/plugins/markasreadplugin.py | 76 ----------------------------- 3 files changed, 17 insertions(+), 79 deletions(-) delete mode 100644 Mailnag/plugins/markasreadplugin.py diff --git a/Mailnag/common/config.py b/Mailnag/common/config.py index fcbdf2d..f638537 100644 --- a/Mailnag/common/config.py +++ b/Mailnag/common/config.py @@ -26,6 +26,7 @@ mailnag_defaults = { 'poll_interval' : '10', 'imap_idle_timeout' : '10', 'autostart' : '1', + 'mark_imap_read' : '1', 'connectivity_test' : 'auto', 'enabled_plugins' : 'dbusplugin, soundplugin, libnotifyplugin' } diff --git a/Mailnag/daemon/mailnagdaemon.py b/Mailnag/daemon/mailnagdaemon.py index 07b759a..d452a3b 100644 --- a/Mailnag/daemon/mailnagdaemon.py +++ b/Mailnag/daemon/mailnagdaemon.py @@ -126,9 +126,22 @@ class MailnagDaemon(MailnagController): # Part of MailnagController interface def mark_mail_as_read(self, mail_id): - # Note: ensure_not_disposed() is not really necessary here - # (the memorizer object is available in dispose()), - # but better be consistent with other daemon methods. + mails = self._mailchecker._all_mails + found = False + for mail in mails: + if mail_id == mail.id: + found = True + break + if (not found) or (not bool(int(self._cfg.get('core', 'mark_imap_read')))): + self._ensure_not_disposed() + self._memorizer.set_to_seen(mail_id) + self._memorizer.save() + return + backend = mail.account._get_backend() + if type(backend).__name__ == 'IMAPMailboxBackend': + mailid = mail.strID + conn = backend._conn + status, res = conn.uid("STORE", mailid, "+FLAGS", "(\Seen)") self._ensure_not_disposed() self._memorizer.set_to_seen(mail_id) self._memorizer.save() diff --git a/Mailnag/plugins/markasreadplugin.py b/Mailnag/plugins/markasreadplugin.py deleted file mode 100644 index 12d8fac..0000000 --- a/Mailnag/plugins/markasreadplugin.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright 2013 - 2020 Andreas Angerer -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. -# - -import os -import dbus -import threading -from Mailnag.common.plugins import Plugin -from Mailnag.common.i18n import _ - - -class MarkAsReadPlugin(Plugin): - def _mark_mail_as_read(self, mail_id): - mails = self.controller._mailchecker._all_mails - found = False - for mail in mails: - if mail_id == mail.id: - found = True - break - if not found: - self.controller._ensure_not_disposed() - self.controller._memorizer.set_to_seen(mail_id) - self.controller._memorizer.save() - return - backend = mail.account._get_backend() - mailid = mail.strID - conn = backend._conn - if type(backend).__name__ == 'IMAPMailboxBackend': - status, res = conn.uid("STORE", mailid, "+FLAGS", "(\Seen)") - self.controller._ensure_not_disposed() - self.controller._memorizer.set_to_seen(mail_id) - self.controller._memorizer.save() - - - - def _mark_mail_as_read_bak(self, mail_id, mail): - self.controller._ensure_not_disposed() - self.controller._memorizer.set_to_seen(mail_id) - self.controller._memorizer.save() - - def enable(self): - self.controller = self.get_mailnag_controller() - self.controller.mark_mail_as_read = self._mark_mail_as_read - - - def disable(self): - self.controller.mark_mail_as_read = self._mark_mail_as_read_bak - - - def get_manifest(self): - return (_("MarkAsReadPlugin"), - _("Marks mails as read on the IMAP server upon clicking them away."), - "1.0", - "Andreas Angerer") - - - def get_default_config(self): - return {} - - - def has_config_ui(self): - return False