Some fixes and refactoring of last commit

This commit is contained in:
Patrick Ulbrich
2020-11-04 20:41:35 +01:00
parent b59ef66aba
commit a124d14be8
13 changed files with 106 additions and 39 deletions

View File

@@ -14,6 +14,7 @@ Popper was written by Ralf Hersel <ralf.hersel@gmx.net>.
Code, docs and packaging contributors:
======================================
Amin Bandali <me@aminb.org>
Andreas Angerer
Balló György <ballogyor@gmail.com>
Dan Christensen <jdc@uwo.ca>
Edwin Smulders

View File

@@ -1,3 +1,4 @@
# Copyright 2020 Patrick Ulbrich <zulu99@gmx.net>
# Copyright 2016 Timo Kankare <timo.kankare@iki.fi>
#
# This program is free software; you can redistribute it and/or modify
@@ -50,7 +51,7 @@ class MailboxBackend(object, metaclass=ABCMeta):
@abstractmethod
def list_messages(self):
"""Lists unseen messages from the mailbox for this account.
Yields tuples (folder, message) for every message.
Yields tuples (folder, message, flags) for every message.
"""
raise NotImplementedError
@@ -61,6 +62,18 @@ class MailboxBackend(object, metaclass=ABCMeta):
"""
raise NotImplementedError
def supports_mark_as_seen(self):
"""Returns True if mailbox supports flagging mails as seen."""
# Default implementation
return False
@abstractmethod
def mark_as_seen(self, mails):
"""Asks mailbox to flag mails in the list as seen.
This may raise an exception if mailbox does not support this action.
"""
raise NotImplementedError
def supports_notifications(self):
"""Returns True if mailbox supports notifications."""
# Default implementation

View File

@@ -1,4 +1,5 @@
# Copyright 2011 - 2019 Patrick Ulbrich <zulu99@gmx.net>
# Copyright 2011 - 2020 Patrick Ulbrich <zulu99@gmx.net>
# Copyright 2020 Andreas Angerer
# Copyright 2016 Timo Kankare <timo.kankare@iki.fi>
# Copyright 2016 Thomas Haider <t.haider@deprecate.de>
# Copyright 2011 Ralf Hersel <ralf.hersel@gmx.net>
@@ -82,7 +83,7 @@ class IMAPMailboxBackend(MailboxBackend):
for folder in folder_list:
# select IMAP folder
conn.select(f'"{folder}"')
conn.select(f'"{folder}"', readonly = True)
try:
status, data = conn.uid('SEARCH', None, '(UNSEEN)') # ALL or UNSEEN
except:
@@ -101,7 +102,7 @@ class IMAPMailboxBackend(MailboxBackend):
except:
logging.debug("Couldn't get IMAP message.")
continue
yield (folder, msg, num.decode("utf-8"))
yield (folder, msg, { 'uid' : num.decode("utf-8"), 'folder' : folder })
def request_folders(self):
@@ -128,11 +129,34 @@ class IMAPMailboxBackend(MailboxBackend):
return lst
def supports_mark_as_seen(self):
return True
def mark_as_seen(self, mails):
# Always create a new connection as an existing one may
# be used for IMAP IDLE.
conn = self._connect()
try:
last_folder = ''
for m in mails:
if ('uid' in m.flags) and ('folder' in m.flags):
folder = m.flags['folder']
if folder != last_folder:
conn.select(f'"{folder}"', readonly = False)
last_folder = folder
status, data = conn.uid("STORE", m.flags['uid'], "+FLAGS", "(\Seen)")
finally:
self._disconnect(conn)
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()
@@ -225,7 +249,7 @@ class IMAPMailboxBackend(MailboxBackend):
folder = self.folders[0]
else:
folder = "INBOX"
conn.select(f'"{folder}"')
conn.select(f'"{folder}"', readonly = True)
def _ensure_open(self):
if not self.is_open():

View File

@@ -1,3 +1,4 @@
# Copyright 2020 Patrick Ulbrich <zulu99@gmx.net>
# Copyright 2016 Timo Kankare <timo.kankare@iki.fi>
#
# This program is free software; you can redistribute it and/or modify
@@ -61,7 +62,7 @@ class MBoxBackend(MailboxBackend):
try:
for msg in mbox:
if 'R' not in msg.get_flags():
yield folder, msg
yield (folder, msg, {})
finally:
mbox.close()
@@ -71,6 +72,15 @@ class MBoxBackend(MailboxBackend):
raise NotImplementedError("mbox does not support folders")
def supports_mark_as_seen(self):
return False
def mark_as_seen(self, mails):
# TODO: local mailboxes should support this
raise NotImplementedError
def notify_next_change(self, callback=None, timeout=None):
raise NotImplementedError("mbox does not support notifications")

View File

@@ -117,13 +117,21 @@ class POP3MailboxBackend(MailboxBackend):
except:
logging.debug("Couldn't get msg from POP message.")
continue
yield (folder, msg)
yield (folder, msg, {})
def request_folders(self):
raise NotImplementedError("POP3 does not support folders")
def supports_mark_as_seen(self):
return False
def mark_as_seen(self, mails):
raise NotImplementedError
def notify_next_change(self, callback=None, timeout=None):
raise NotImplementedError("POP3 does not support notifications")

View File

@@ -1,4 +1,4 @@
# Copyright 2011 - 2019 Patrick Ulbrich <zulu99@gmx.net>
# Copyright 2011 - 2020 Patrick Ulbrich <zulu99@gmx.net>
# Copyright 2016 Thomas Haider <t.haider@deprecate.de>
# Copyright 2016, 2018 Timo Kankare <timo.kankare@iki.fi>
# Copyright 2011 Ralf Hersel <ralf.hersel@gmx.net>
@@ -141,6 +141,14 @@ class Account:
return self._get_backend().request_folders()
def supports_mark_as_seen(self):
return self._get_backend().supports_mark_as_seen()
def mark_as_seen(self, mails):
self._get_backend().mark_as_seen(mails)
def get_id(self):
"""Returns unique id for the account."""
# Assumption: The name of the account is unique.

View File

@@ -26,7 +26,6 @@ mailnag_defaults = {
'poll_interval' : '10',
'imap_idle_timeout' : '10',
'autostart' : '1',
'mark_imap_read' : '1',
'connectivity_test' : 'auto',
'enabled_plugins' : 'dbusplugin, soundplugin, libnotifyplugin'
}

View File

@@ -278,6 +278,7 @@ class ConfigWindow:
aboutdialog.set_license_type(Gtk.License.GPL_2_0)
aboutdialog.set_authors([
"Patrick Ulbrich (maintainer)",
"Andreas Angerer",
"Balló György",
"Dan Christensen",
"Edwin Smulders",

View File

@@ -108,9 +108,9 @@ class DBusService(dbus.service.Object):
d['subject'] = m.subject # string (s)
d['sender_name'] = name # string (s)
d['sender_addr'] = addr # string (s)
d['account_name'] = m.account_name # 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

View File

@@ -1,4 +1,5 @@
# Copyright 2011 - 2020 Patrick Ulbrich <zulu99@gmx.net>
# Copyright 2020 Andreas Angerer
# Copyright 2011 Ralf Hersel <ralf.hersel@gmx.net>
#
# This program is free software; you can redistribute it and/or modify
@@ -36,7 +37,6 @@ class MailChecker:
self._conntest = conntest
self._dbus_service = dbus_service
self._count_on_last_check = 0
self._all_mails = []
def check(self, accounts):
@@ -55,6 +55,7 @@ class MailChecker:
all_mails = self._mailsyncer.sync(accounts)
unseen_mails = []
new_mails = []
seen_mails_by_account = {}
for mail in all_mails:
if self._memorizer.contains(mail.id): # mail was fetched before
@@ -62,11 +63,24 @@ class MailChecker:
unseen_mails.append(mail)
if self._firstcheck:
new_mails.append(mail)
else:
# if the mail account supports tagging mails as seen (e.g. IMAP),
# mark the mail as seen on the server as well.
if mail.account.supports_mark_as_seen():
if not mail.account in seen_mails_by_account:
seen_mails_by_account[mail.account] = []
seen_mails_by_account[mail.account].append(mail)
else: # mail is fetched the first time
unseen_mails.append(mail)
new_mails.append(mail)
self._all_mails = all_mails
# Flag mails to seen on server
for acc, mails in seen_mails_by_account.items():
try:
acc.mark_as_seen(mails)
except:
logging.warning("Failed to set mails to seen on server (account: '%s').", acc.name)
self._memorizer.sync(all_mails)
self._memorizer.save()
self._firstcheck = False

View File

@@ -1,5 +1,6 @@
# Copyright 2016 Timo Kankare <timo.kankare@iki.fi>
# Copyright 2014 - 2020 Patrick Ulbrich <zulu99@gmx.net>
# Copyright 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
@@ -126,23 +127,11 @@ class MailnagDaemon(MailnagController):
# Part of MailnagController interface
def mark_mail_as_read(self, mail_id):
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)")
# Note: ensure_not_disposed() is not really necessary here
# (the memorizer object is available in dispose()),
# but better be consistent with other daemon methods.
self._ensure_not_disposed()
self._memorizer.set_to_seen(mail_id)
self._memorizer.save()

View File

@@ -1,4 +1,5 @@
# Copyright 2011 - 2019 Patrick Ulbrich <zulu99@gmx.net>
# Copyright 2011 - 2020 Patrick Ulbrich <zulu99@gmx.net>
# Copyright 2020 Andreas Angerer
# Copyright 2016, 2018 Timo Kankare <timo.kankare@iki.fi>
# Copyright 2011 Leighton Earl <leighton.earl@gmx.com>
# Copyright 2011 Ralf Hersel <ralf.hersel@gmx.net>
@@ -35,15 +36,13 @@ from Mailnag.common.config import cfg_folder
# Mail class
#
class Mail:
def __init__(self, datetime, subject, sender, id, account, strID):
def __init__(self, datetime, subject, sender, id, account, flags):
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
self.flags = flags
#
@@ -68,7 +67,7 @@ class MailCollector:
logging.error("Failed to open mailbox for account '%s' (%s)." % (acc.name, ex))
continue
for folder, msg, num in acc.list_messages():
for folder, msg, flags in acc.list_messages():
sender, subject, datetime, msgid = self._get_header(msg)
id = self._get_id(msgid, acc, folder, sender, subject, datetime)
@@ -80,7 +79,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, num))
sender, id, acc, flags))
mail_ids[id] = None
# leave account with notifications open, so that it can
@@ -195,12 +194,13 @@ 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:
tmp[acc.get_id()] = {}
for mail in rcv_lst:
tmp[mail.account_id][mail.id] = mail
tmp[mail.account.get_id()][mail.id] = mail
# compare current mails against received mails
# and remove those that are gone (probably opened in mail client).

View File

@@ -120,7 +120,7 @@ class UserscriptPlugin(Plugin):
sender_name, sender_addr = m.sender
if len(sender_addr) == 0: sender_addr = 'UNKNOWN_SENDER'
script_args.append(m.account_name)
script_args.append(m.account.name)
script_args.append(sender_addr)
script_args.append(m.subject)
start_subprocess(script_args)