From 625a937ad5ea09fec27e8667f995c7371e20b23f Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 13 Jan 2014 13:20:00 -0400 Subject: Add check for uniqueness when adding mails. Check by mbox + content-hash --- mail/changes/bug_4949-check-fdoc-uniqueness | 2 ++ mail/src/leap/mail/imap/fields.py | 4 +++ mail/src/leap/mail/imap/mailbox.py | 6 ++-- mail/src/leap/mail/imap/messages.py | 50 +++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 mail/changes/bug_4949-check-fdoc-uniqueness (limited to 'mail') diff --git a/mail/changes/bug_4949-check-fdoc-uniqueness b/mail/changes/bug_4949-check-fdoc-uniqueness new file mode 100644 index 00000000..bf49d1f8 --- /dev/null +++ b/mail/changes/bug_4949-check-fdoc-uniqueness @@ -0,0 +1,2 @@ + o Check for flags doc uniqueness before adding a message. Avoids duplicates of + a single message in the same mailbox while copying or moving. Closes: #4949 diff --git a/mail/src/leap/mail/imap/fields.py b/mail/src/leap/mail/imap/fields.py index 2545adfa..70af61f6 100644 --- a/mail/src/leap/mail/imap/fields.py +++ b/mail/src/leap/mail/imap/fields.py @@ -99,6 +99,7 @@ class WithMsgFields(object): TYPE_MBOX_SEEN_IDX = 'by-type-and-mbox-and-seen' TYPE_MBOX_RECT_IDX = 'by-type-and-mbox-and-recent' TYPE_MBOX_DEL_IDX = 'by-type-and-mbox-and-deleted' + TYPE_MBOX_C_HASH_IDX = 'by-type-and-mbox-and-contenthash' TYPE_C_HASH_IDX = 'by-type-and-contenthash' TYPE_C_HASH_PART_IDX = 'by-type-and-contenthash-and-partnumber' TYPE_P_HASH_IDX = 'by-type-and-payloadhash' @@ -121,6 +122,9 @@ class WithMsgFields(object): # mailboxes TYPE_SUBS_IDX: [KTYPE, 'bool(subscribed)'], + # fdocs uniqueness + TYPE_MBOX_C_HASH_IDX: [KTYPE, MBOX_VAL, CHASH_VAL], + # content, headers doc TYPE_C_HASH_IDX: [KTYPE, CHASH_VAL], diff --git a/mail/src/leap/mail/imap/mailbox.py b/mail/src/leap/mail/imap/mailbox.py index 7c014901..c9e86847 100644 --- a/mail/src/leap/mail/imap/mailbox.py +++ b/mail/src/leap/mail/imap/mailbox.py @@ -125,7 +125,7 @@ class SoledadMailbox(WithMsgFields, MBoxParser): def addListener(self, listener): """ - Adds a listener to the listeners queue. + Add a listener to the listeners queue. The server adds itself as a listener when there is a SELECT, so it can send EXIST commands. @@ -137,7 +137,7 @@ class SoledadMailbox(WithMsgFields, MBoxParser): def removeListener(self, listener): """ - Removes a listener from the listeners queue. + Remove a listener from the listeners queue. :param listener: listener to remove :type listener: an object that implements IMailboxListener @@ -146,7 +146,7 @@ class SoledadMailbox(WithMsgFields, MBoxParser): def _get_mbox(self): """ - Returns mailbox document. + Return mailbox document. :return: A SoledadDocument containing this mailbox, or None if the query failed. diff --git a/mail/src/leap/mail/imap/messages.py b/mail/src/leap/mail/imap/messages.py index 37e43118..a3fcd872 100644 --- a/mail/src/leap/mail/imap/messages.py +++ b/mail/src/leap/mail/imap/messages.py @@ -1064,10 +1064,27 @@ class MessageCollection(WithMsgFields, IndexedDB, MailParser, MBoxParser): hd[self.DATE_KEY] = date return hd + def _fdoc_already_exists(self, chash): + """ + Check whether we can find a flags doc for this mailbox with the + given content-hash. It enforces that we can only have the same maessage + listed once for a a given mailbox. + + :param chash: the content-hash to check about. + :type chash: basestring + :return: False, if it does not exist, or UID. + """ + exist = self._get_fdoc_from_chash(chash) + if exist: + return exist.content.get(fields.UID_KEY, "unknown-uid") + else: + return False + @deferred def add_msg(self, raw, subject=None, flags=None, date=None, uid=1): """ Creates a new message document. + Here lives the magic of the leap mail. Well, in soledad, really. :param raw: the raw message :type raw: str @@ -1097,6 +1114,14 @@ class MessageCollection(WithMsgFields, IndexedDB, MailParser, MBoxParser): # parse msg, chash, size, multi = self._do_parse(raw) + # check for uniqueness. + if self._fdoc_already_exists(chash): + logger.warning("We already have that message in this mailbox.") + # note that this operation will leave holes in the UID sequence, + # but we're gonna change that all the same for a local-only table. + # so not touch it by the moment. + return False + fd = self._populate_flags(flags, uid, chash, size, multi) hd = self._populate_headr(msg, chash, subject, date) @@ -1156,6 +1181,31 @@ class MessageCollection(WithMsgFields, IndexedDB, MailParser, MBoxParser): # getters + def _get_fdoc_from_chash(self, chash): + """ + Return a flags document for this mailbox with a given chash. + + :return: A SoledadDocument containing the Flags Document, or None if + the query failed. + :rtype: SoledadDocument or None. + """ + try: + query = self._soledad.get_from_index( + fields.TYPE_MBOX_C_HASH_IDX, + fields.TYPE_FLAGS_VAL, self.mbox, chash) + if query: + if len(query) > 1: + logger.warning( + "More than one fdoc found for this chash, " + "we got a duplicate!!") + # XXX we could take action, like trigger a background + # process to kill dupes. + return query.pop() + else: + return None + except Exception as exc: + logger.exception("Unhandled error %r" % exc) + def get_msg_by_uid(self, uid): """ Retrieves a LeapMessage by UID. -- cgit v1.2.3