diff options
-rw-r--r-- | mail/src/leap/mail/imap/mailbox.py | 11 | ||||
-rw-r--r-- | mail/src/leap/mail/imap/messages.py | 250 | ||||
-rw-r--r-- | mail/src/leap/mail/imap/tests/test_imap.py | 130 |
3 files changed, 7 insertions, 384 deletions
diff --git a/mail/src/leap/mail/imap/mailbox.py b/mail/src/leap/mail/imap/mailbox.py index 2653ae49..91c6549f 100644 --- a/mail/src/leap/mail/imap/mailbox.py +++ b/mail/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/mail/src/leap/mail/imap/messages.py b/mail/src/leap/mail/imap/messages.py index b7bb6ee8..02aac2ef 100644 --- a/mail/src/leap/mail/imap/messages.py +++ b/mail/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/mail/src/leap/mail/imap/tests/test_imap.py b/mail/src/leap/mail/imap/tests/test_imap.py index 802bc9d1..fbe02d40 100644 --- a/mail/src/leap/mail/imap/tests/test_imap.py +++ b/mail/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 |