summaryrefslogtreecommitdiff
path: root/mail
diff options
context:
space:
mode:
Diffstat (limited to 'mail')
-rw-r--r--mail/changes/bug_4461_fix-uid-indexing4
-rw-r--r--mail/src/leap/mail/imap/fetch.py11
-rw-r--r--mail/src/leap/mail/imap/server.py150
3 files changed, 130 insertions, 35 deletions
diff --git a/mail/changes/bug_4461_fix-uid-indexing b/mail/changes/bug_4461_fix-uid-indexing
new file mode 100644
index 0000000..881bb24
--- /dev/null
+++ b/mail/changes/bug_4461_fix-uid-indexing
@@ -0,0 +1,4 @@
+ o Fix several bugs with imap mailbox getUIDNext and notifiers that were breaking
+ the mail indexing after message deletion. This solves also the perceived
+ mismatch between the number of unread mails reported by bitmask_client and
+ the number reported by MUAs. Closes: #4461
diff --git a/mail/src/leap/mail/imap/fetch.py b/mail/src/leap/mail/imap/fetch.py
index bc04bd1..3422ed5 100644
--- a/mail/src/leap/mail/imap/fetch.py
+++ b/mail/src/leap/mail/imap/fetch.py
@@ -141,16 +141,19 @@ class LeapIncomingMail(object):
"""
Starts a loop to fetch mail.
"""
- self._loop = LoopingCall(self.fetch)
- self._loop.start(self._check_period)
+ if self._loop is None:
+ self._loop = LoopingCall(self.fetch)
+ self._loop.start(self._check_period)
+ else:
+ logger.warning("Tried to start an already running fetching loop.")
def stop(self):
"""
Stops the loop that fetches mail.
"""
- # XXX should cancel ongoing fetches too.
if self._loop and self._loop.running is True:
self._loop.stop()
+ self._loop = None
#
# Private methods.
@@ -214,7 +217,7 @@ class LeapIncomingMail(object):
Generic errback
"""
err = failure.value
- logger.error("error!: %r" % (err,))
+ logger.exception("error!: %r" % (err,))
def _decryption_error(self, failure):
"""
diff --git a/mail/src/leap/mail/imap/server.py b/mail/src/leap/mail/imap/server.py
index 11f3ccf..bb2830d 100644
--- a/mail/src/leap/mail/imap/server.py
+++ b/mail/src/leap/mail/imap/server.py
@@ -23,6 +23,7 @@ import StringIO
import cStringIO
import time
+from collections import defaultdict
from email.parser import Parser
from zope.interface import implements
@@ -241,6 +242,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB):
:rtype: SoledadDocument
"""
+ # XXX only upper for INBOX ---
name = name.upper()
doc = self._soledad.get_from_index(
self.TYPE_MBOX_IDX, self.MBOX_KEY, name)
@@ -274,6 +276,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB):
:returns: a a SoledadMailbox instance
:rtype: SoledadMailbox
"""
+ # XXX only upper for INBOX
name = name.upper()
if name not in self.mailboxes:
raise imap4.MailboxException("No such mailbox")
@@ -299,6 +302,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB):
:returns: True if successful
:rtype: bool
"""
+ # XXX only upper for INBOX
name = name.upper()
# XXX should check mailbox name for RFC-compliant form
@@ -360,6 +364,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB):
:rtype: bool
"""
+ # XXX only upper for INBOX
name = name.upper()
if name not in self.mailboxes:
@@ -385,6 +390,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB):
names. use with care.
:type force: bool
"""
+ # XXX only upper for INBOX
name = name.upper()
if not name in self.mailboxes:
raise imap4.MailboxException("No such mailbox")
@@ -422,6 +428,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB):
:param newname: new name of the mailbox
:type newname: str
"""
+ # XXX only upper for INBOX
oldname = oldname.upper()
newname = newname.upper()
@@ -487,7 +494,6 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB):
# maybe we should store subscriptions in another
# document...
if not name in self.mailboxes:
- print "not this mbox"
self.addMailbox(name)
mbox = self._get_mailbox_by_name(name)
@@ -785,6 +791,7 @@ class LeapMessage(WithMsgFields):
return dict(filter_by_cond)
# --- no multipart for now
+ # XXX Fix MULTIPART SUPPORT!
def isMultipart(self):
return False
@@ -967,6 +974,7 @@ class MessageCollection(WithMsgFields, IndexedDB):
docs = self._soledad.get_from_index(
SoledadBackedAccount.TYPE_MBOX_UID_IDX,
self.TYPE_MESSAGE_VAL, self.mbox, str(uid))
+
return docs[0] if docs else None
def get_msg_by_uid(self, uid):
@@ -984,6 +992,47 @@ class MessageCollection(WithMsgFields, IndexedDB):
if doc:
return LeapMessage(doc)
+ def get_by_index(self, index):
+ """
+ Retrieves a mesage document by mailbox index.
+
+ :param index: the index of the sequence (zero-indexed)
+ :type index: int
+ """
+ try:
+ return self.get_all()[index]
+ except IndexError:
+ return None
+
+ def get_msg_by_index(self, index):
+ """
+ Retrieves a LeapMessage by sequence index.
+
+ :param index: the index of the sequence (zero-indexed)
+ :type index: int
+ """
+ doc = self.get_by_index(index)
+ if doc:
+ return LeapMessage(doc)
+
+ def is_deleted(self, doc):
+ """
+ Returns whether a given doc is deleted or not.
+
+ :param doc: the document to check
+ :rtype: bool
+ """
+ return self.DELETED_FLAG in doc.content[self.FLAGS_KEY]
+
+ def get_last(self):
+ """
+ Gets the last LeapMessage
+ """
+ _all = self.get_all()
+ if not _all:
+ return None
+ return LeapMessage(_all[-1])
+
def get_all(self):
"""
Get all message documents for the selected mailbox.
@@ -993,9 +1042,13 @@ class MessageCollection(WithMsgFields, IndexedDB):
:rtype: list of SoledadDocument
"""
# XXX this should return LeapMessage instances
- return self._soledad.get_from_index(
+ all_docs = [doc for doc in self._soledad.get_from_index(
SoledadBackedAccount.TYPE_MBOX_IDX,
- self.TYPE_MESSAGE_VAL, self.mbox)
+ self.TYPE_MESSAGE_VAL, self.mbox)]
+ #if not self.is_deleted(doc)]
+ # highly inneficient, but first let's grok it and then
+ # let's worry about efficiency.
+ return sorted(all_docs, key=lambda item: item.content['uid'])
def unseen_iter(self):
"""
@@ -1075,8 +1128,11 @@ class MessageCollection(WithMsgFields, IndexedDB):
:return: LeapMessage or None if not found.
:rtype: LeapMessage
"""
+ #try:
+ #return self.get_msg_by_uid(uid)
try:
- return self.get_msg_by_uid(uid)
+ return [doc
+ for doc in self.get_all()][uid - 1]
except IndexError:
return None
@@ -1116,7 +1172,7 @@ class SoledadMailbox(WithMsgFields):
CMD_UIDVALIDITY = "UIDVALIDITY"
CMD_UNSEEN = "UNSEEN"
- listeners = []
+ _listeners = defaultdict(set)
def __init__(self, mbox, soledad=None, rw=1):
"""
@@ -1150,10 +1206,18 @@ class SoledadMailbox(WithMsgFields):
if not self.getFlags():
self.setFlags(self.INIT_FLAGS)
- # the server itself is a listener to the mailbox.
- # so we can notify it (and should!) after chanes in flags
- # and number of messages.
- map(lambda i: self.listeners.remove(i), self.listeners)
+ @property
+ def listeners(self):
+ """
+ Returns listeners for this mbox.
+
+ The server itself is a listener to the mailbox.
+ so we can notify it (and should!) after changes in flags
+ and number of messages.
+
+ :rtype: set
+ """
+ return self._listeners[self.mbox]
def addListener(self, listener):
"""
@@ -1163,7 +1227,7 @@ class SoledadMailbox(WithMsgFields):
:type listener: an object that implements IMailboxListener
"""
logger.debug('adding mailbox listener: %s' % listener)
- self.listeners.append(listener)
+ self.listeners.add(listener)
def removeListener(self, listener):
"""
@@ -1172,25 +1236,24 @@ class SoledadMailbox(WithMsgFields):
:param listener: listener to remove
:type listener: an object that implements IMailboxListener
"""
- logger.debug('removing mailbox listener: %s' % listener)
- try:
- self.listeners.remove(listener)
- except ValueError:
- logger.error(
- "removeListener: cannot remove listener %s" % listener)
+ self.listeners.remove(listener)
def _get_mbox(self):
"""
Returns mailbox document.
- :return: A SoledadDocument containing this mailbox.
- :rtype: SoledadDocument
+ :return: A SoledadDocument containing this mailbox, or None if
+ the query failed.
+ :rtype: SoledadDocument or None.
"""
- query = self._soledad.get_from_index(
- SoledadBackedAccount.TYPE_MBOX_IDX,
- self.TYPE_MBOX_VAL, self.mbox)
- if query:
- return query.pop()
+ try:
+ query = self._soledad.get_from_index(
+ SoledadBackedAccount.TYPE_MBOX_IDX,
+ self.TYPE_MBOX_VAL, self.mbox)
+ if query:
+ return query.pop()
+ except Exception as exc:
+ logger.error("Unhandled error %r" % exc)
def getFlags(self):
"""
@@ -1287,8 +1350,12 @@ class SoledadMailbox(WithMsgFields):
:rtype: int
"""
- # XXX reimplement with proper index
- return self.messages.count() + 1
+ last = self.messages.get_last()
+ if last:
+ nextuid = last.getUID() + 1
+ else:
+ nextuid = 1
+ return nextuid
def getMessageCount(self):
"""
@@ -1375,6 +1442,8 @@ class SoledadMailbox(WithMsgFields):
self.messages.add_msg(message, flags=flags, date=date,
uid=uid_next)
+
+ # XXX recent should not include deleted...??
exists = len(self.messages)
recent = len(self.messages.get_recent())
for listener in self.listeners:
@@ -1434,16 +1503,35 @@ class SoledadMailbox(WithMsgFields):
:rtype: A tuple of two-tuples of message sequence numbers and
LeapMessage
"""
- # XXX implement sequence numbers (uid = 0)
result = []
+ sequence = True if uid == 0 else False
if not messages.last:
- messages.last = self.messages.count()
+ try:
+ iter(messages)
+ except TypeError:
+ # looks like we cannot iterate
+ last = self.messages.get_last()
+ uid_last = last.getUID()
+ messages.last = uid_last
+
+ # for sequence numbers (uid = 0)
+ if sequence:
+ for msg_id in messages:
+ msg = self.messages.get_msg_by_index(msg_id - 1)
+ if msg:
+ result.append((msg.getUID(), msg))
+ else:
+ print "fetch %s, no msg found!!!" % msg_id
+
+ else:
+ for msg_id in messages:
+ msg = self.messages.get_msg_by_uid(msg_id)
+ if msg:
+ result.append((msg_id, msg))
+ else:
+ print "fetch %s, no msg found!!!" % msg_id
- for msg_id in messages:
- msg = self.messages.get_msg_by_uid(msg_id)
- if msg:
- result.append((msg_id, msg))
return tuple(result)
def _signal_unread_to_ui(self):