summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/leap/mail/imap/mailbox.py11
-rw-r--r--src/leap/mail/imap/messages.py250
-rw-r--r--src/leap/mail/imap/tests/test_imap.py130
3 files changed, 7 insertions, 384 deletions
diff --git a/src/leap/mail/imap/mailbox.py b/src/leap/mail/imap/mailbox.py
index 2653ae4..91c6549 100644
--- a/src/leap/mail/imap/mailbox.py
+++ b/src/leap/mail/imap/mailbox.py
@@ -82,9 +82,9 @@ class IMAPMailbox(object):
A Soledad-backed IMAP mailbox.
Implements the high-level method needed for the Mailbox interfaces.
- The low-level database methods are contained in IMAPMessageCollection
- class, which we instantiate and make accessible in the `messages`
- attribute.
+ The low-level database methods are contained in the generic
+ MessageCollection class. We receive an instance of it and it is made
+ accessible in the `collection` attribute.
"""
implements(
imap4.IMailbox,
@@ -107,14 +107,13 @@ class IMAPMailbox(object):
def __init__(self, collection, rw=1):
"""
- :param collection: instance of IMAPMessageCollection
- :type collection: IMAPMessageCollection
+ :param collection: instance of MessageCollection
+ :type collection: MessageCollection
:param rw: read-and-write flag for this mailbox
:type rw: int
"""
self.rw = rw
-
self._uidvalidity = None
self.collection = collection
diff --git a/src/leap/mail/imap/messages.py b/src/leap/mail/imap/messages.py
index b7bb6ee..02aac2e 100644
--- a/src/leap/mail/imap/messages.py
+++ b/src/leap/mail/imap/messages.py
@@ -15,23 +15,20 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
-IMAPMessage and IMAPMessageCollection.
+IMAPMessage implementation.
"""
import logging
from twisted.mail import imap4
from twisted.internet import defer
from zope.interface import implements
-from leap.common.check import leap_assert, leap_assert_type
from leap.mail.utils import find_charset
logger = logging.getLogger(__name__)
-# TODO ------------------------------------------------------------
-
+# TODO
# [ ] Add ref to incoming message during add_msg.
-# [ ] Delete incoming mail only after successful write.
class IMAPMessage(object):
@@ -251,246 +248,3 @@ def _format_headers(headers, negate, *names):
if cond(key):
_headers[key] = value
return _headers
-
-
-class IMAPMessageCollection(object):
- """
- A collection of messages, surprisingly.
-
- It is tied to a selected mailbox name that is passed to its constructor.
- Implements a filter query over the messages contained in a soledad
- database.
- """
-
- messageklass = IMAPMessage
-
- # TODO
- # [ ] Add RECENT flags docs to mailbox-doc attributes (list-of-uids)
- # [ ] move Query for all the headers documents to Collection
-
- # TODO this should be able to produce a MessageSet methinks
- # TODO --- reimplement, review and prune documentation below.
-
- FLAGS_DOC = "FLAGS"
- HEADERS_DOC = "HEADERS"
- CONTENT_DOC = "CONTENT"
- """
- RECENT_DOC is a document that stores a list of the UIDs
- with the recent flag for this mailbox. It deserves a special treatment
- because:
- (1) it cannot be set by the user
- (2) it's a flag that we set inmediately after a fetch, which is quite
- often.
- (3) we need to be able to set/unset it in batches without doing a single
- write for each element in the sequence.
- """
- RECENT_DOC = "RECENT"
- """
- HDOCS_SET_DOC is a document that stores a set of the Document-IDs
- (the u1db index) for all the headers documents for a given mailbox.
- We use it to prefetch massively all the headers for a mailbox.
- This is the second massive query, after fetching all the FLAGS, that
- a typical IMAP MUA will do in a case where we do not have local disk cache.
- """
- HDOCS_SET_DOC = "HDOCS_SET"
-
- def __init__(self, collection):
- """
- Constructor for IMAPMessageCollection.
-
- :param collection: an instance of a MessageCollection
- :type collection: MessageCollection
- """
- leap_assert(
- collection.is_mailbox_collection(),
- "Need a mailbox name to initialize")
- mbox_name = collection.mbox_name
- leap_assert(mbox_name.strip() != "", "mbox cannot be blank space")
- leap_assert(isinstance(mbox_name, (str, unicode)),
- "mbox needs to be a string")
- self.collection = collection
-
- # XXX this has to be done in IMAPAccount
- # (Where the collection must be instantiated and passed to us)
- # self.mbox = normalize_mailbox(mbox)
-
- @property
- def mbox_name(self):
- """
- Return the string that identifies this mailbox.
- """
- return self.collection.mbox_name
-
- def add_msg(self, raw, flags=None, date=None):
- """
- Creates a new message document.
-
- :param raw: the raw message
- :type raw: str
-
- :param flags: flags
- :type flags: list
-
- :param date: the received date for the message
- :type date: str
-
- :return: a deferred that will be fired with the message
- uid when the adding succeed.
- :rtype: deferred
- """
- if flags is None:
- flags = tuple()
- leap_assert_type(flags, tuple)
- return self.collection.add_msg(raw, flags, date)
-
- def get_msg_by_uid(self, uid, absolute=True):
- """
- Retrieves a IMAPMessage by UID.
- This is used primarity in the Mailbox fetch and store methods.
-
- :param uid: the message uid to query by
- :type uid: int
-
- :rtype: IMAPMessage
- """
- def make_imap_msg(msg):
- kls = self.messageklass
- # TODO --- remove ref to collection
- return kls(msg, self.collection)
-
- d = self.collection.get_msg_by_uid(uid, absolute=absolute)
- d.addCalback(make_imap_msg)
- return d
-
-
- # TODO -- move this to collection too
- # Used for the Search (Drafts) queries?
- def _get_uid_from_msgid(self, msgid):
- """
- Return a UID for a given message-id.
-
- It first gets the headers-doc for that msg-id, and
- it found it queries the flags doc for the current mailbox
- for the matching content-hash.
-
- :return: A UID, or None
- """
- return self._get_uid_from_msgidCb(msgid)
-
- # TODO handle deferreds
- def set_flags(self, messages, flags, mode):
- """
- Set flags for a sequence of messages.
-
- :param mbox: the mbox this message belongs to
- :type mbox: str or unicode
- :param messages: the messages to iterate through
- :type messages: sequence
- :flags: the flags to be set
- :type flags: tuple
- :param mode: the mode for setting. 1 is append, -1 is remove, 0 set.
- :type mode: int
- :param observer: a deferred that will be called with the dictionary
- mapping UIDs to flags after the operation has been
- done.
- :type observer: deferred
- """
- getmsg = self.get_msg_by_uid
-
- def set_flags(uid, flags, mode):
- msg = getmsg(uid)
- if msg is not None:
- # XXX IMAPMessage needs access to the collection
- # to be able to set flags. Better if we make use
- # of collection... here.
- return uid, msg.setFlags(flags, mode)
-
- setted_flags = [set_flags(uid, flags, mode) for uid in messages]
- result = dict(filter(None, setted_flags))
- # XXX return gatherResults or something
- return result
-
- def count(self):
- """
- Return the count of messages for this mailbox.
-
- :rtype: int
- """
- return self.collection.count()
-
- # headers query
-
- def all_headers(self):
- """
- Return a dict with all the header documents for this
- mailbox.
-
- :rtype: dict
- """
- # Use self.collection.mbox_indexer
- # and derive all the doc_ids for the hdocs
- raise NotImplementedError()
-
- # unseen messages
-
- def unseen_iter(self):
- """
- Get an iterator for the message UIDs with no `seen` flag
- for this mailbox.
-
- :return: iterator through unseen message doc UIDs
- :rtype: iterable
- """
- raise NotImplementedError()
-
- def count_unseen(self):
- """
- Count all messages with the `Unseen` flag.
-
- :returns: count
- :rtype: int
- """
- return len(list(self.unseen_iter()))
-
- def get_unseen(self):
- """
- Get all messages with the `Unseen` flag
-
- :returns: a list of LeapMessages
- :rtype: list
- """
- raise NotImplementedError()
- #return [self.messageklass(self._soledad, doc_id, self.mbox)
- #for doc_id in self.unseen_iter()]
-
- # recent messages
-
- def count_recent(self):
- """
- Count all messages with the `Recent` flag.
- It just retrieves the length of the recent_flags set,
- which is stored in a specific type of document for
- this collection.
-
- :returns: count
- :rtype: int
- """
- raise NotImplementedError()
-
- # magic
-
- def __len__(self):
- """
- Returns the number of messages on this mailbox.
- :rtype: int
- """
- return self.count()
-
- def __repr__(self):
- """
- Representation string for this object.
- """
- return u"<IMAPMessageCollection: mbox '%s' (%s)>" % (
- self.mbox_name, self.count())
-
- # TODO implement __iter__ ?
diff --git a/src/leap/mail/imap/tests/test_imap.py b/src/leap/mail/imap/tests/test_imap.py
index 802bc9d..fbe02d4 100644
--- a/src/leap/mail/imap/tests/test_imap.py
+++ b/src/leap/mail/imap/tests/test_imap.py
@@ -38,7 +38,6 @@ from twisted.python import failure
from twisted import cred
from leap.mail.imap.mailbox import IMAPMailbox
-from leap.mail.imap.messages import IMAPMessageCollection
from leap.mail.imap.tests.utils import IMAP4HelperMixin
@@ -70,139 +69,10 @@ class TestRealm:
def requestAvatar(self, avatarId, mind, *interfaces):
return imap4.IAccount, self.theAccount, lambda: None
-
#
# TestCases
#
-# TODO rename to IMAPMessageCollection
-class MessageCollectionTestCase(IMAP4HelperMixin):
- """
- Tests for the MessageCollection class
- """
- count = 0
-
- def setUp(self):
- """
- setUp method for each test
- We override mixin method since we are only testing
- MessageCollection interface in this particular TestCase
- """
- # FIXME -- return deferred
- super(MessageCollectionTestCase, self).setUp()
-
- # FIXME --- update initialization
- self.messages = IMAPMessageCollection(
- "testmbox%s" % (self.count,), self._soledad)
- MessageCollectionTestCase.count += 1
-
- def tearDown(self):
- """
- tearDown method for each test
- """
- del self.messages
-
- def testEmptyMessage(self):
- """
- Test empty message and collection
- """
- em = self.messages._get_empty_doc()
- self.assertEqual(
- em,
- {
- "chash": '',
- "deleted": False,
- "flags": [],
- "mbox": "inbox",
- "seen": False,
- "multi": False,
- "size": 0,
- "type": "flags",
- "uid": 1,
- })
- self.assertEqual(self.messages.count(), 0)
-
- def testMultipleAdd(self):
- """
- Add multiple messages
- """
- mc = self.messages
- self.assertEqual(self.messages.count(), 0)
-
- def add_first():
- d = defer.gatherResults([
- mc.add_msg('Stuff 1', subject="test1"),
- mc.add_msg('Stuff 2', subject="test2"),
- mc.add_msg('Stuff 3', subject="test3"),
- mc.add_msg('Stuff 4', subject="test4")])
- return d
-
- def add_second(result):
- d = defer.gatherResults([
- mc.add_msg('Stuff 5', subject="test5"),
- mc.add_msg('Stuff 6', subject="test6"),
- mc.add_msg('Stuff 7', subject="test7")])
- return d
-
- def check_second(result):
- return self.assertEqual(mc.count(), 7)
-
- d1 = add_first()
- d1.addCallback(add_second)
- d1.addCallback(check_second)
-
- def testRecentCount(self):
- """
- Test the recent count
- """
- mc = self.messages
- countrecent = mc.count_recent
- eq = self.assertEqual
-
- self.assertEqual(countrecent(), 0)
-
- d = mc.add_msg('Stuff', subject="test1")
- # For the semantics defined in the RFC, we auto-add the
- # recent flag by default.
-
- def add2(_):
- return mc.add_msg('Stuff', subject="test2",
- flags=('\\Deleted',))
-
- def add3(_):
- return mc.add_msg('Stuff', subject="test3",
- flags=('\\Recent',))
-
- def add4(_):
- return mc.add_msg('Stuff', subject="test4",
- flags=('\\Deleted', '\\Recent'))
-
- d.addCallback(lambda r: eq(countrecent(), 1))
- d.addCallback(add2)
- d.addCallback(lambda r: eq(countrecent(), 2))
- d.addCallback(add3)
- d.addCallback(lambda r: eq(countrecent(), 3))
- d.addCallback(add4)
- d.addCallback(lambda r: eq(countrecent(), 4))
-
- def testFilterByMailbox(self):
- """
- Test that queries filter by selected mailbox
- """
- mc = self.messages
- self.assertEqual(self.messages.count(), 0)
-
- def add_1():
- d1 = mc.add_msg('msg 1', subject="test1")
- d2 = mc.add_msg('msg 2', subject="test2")
- d3 = mc.add_msg('msg 3', subject="test3")
- d = defer.gatherResults([d1, d2, d3])
- return d
-
- add_1().addCallback(lambda ignored: self.assertEqual(
- mc.count(), 3))
-
-
# DEBUG ---
#from twisted.internet.base import DelayedCall
#DelayedCall.debug = True