]> git.droids-corp.org - imapami.git/commitdiff
fix race when receiving mail during rule process
authorOlivier Matz <zer0@droids-corp.org>
Sun, 27 Nov 2016 14:37:18 +0000 (15:37 +0100)
committerOlivier Matz <olivier.matz@6wind.com>
Mon, 28 Nov 2016 09:51:16 +0000 (10:51 +0100)
Get the state (uidnext) for each inbox used in the configuration.
It gives the uid of the next message the will be added in the mbox.
We will only care about messages with a uid lower than this uidnext,
to avoid race conditions with a message arriving while we are in the

Signed-off-by: Olivier Matz <zer0@droids-corp.org>
imapami/__init__.py
imapami/rules.py

index 5000e779518ad8eb4e764a60bf1f64a5f0bb8dfe..b817646791fdbb2f8641792397c0942d41d9629d 100644 (file)
@@ -37,6 +37,7 @@ import imaplib
 import inspect
 import logging
 import pydoc
+import re
 import sys
 import yaml
 
@@ -111,6 +112,7 @@ class Imapami(object):
         self.logger = self._get_logger(loglevel)
         self._load_config(config)
         self._update_logger()
+        self.uidnext = {}
 
     def _get_logger(self, loglevel):
         """
@@ -211,14 +213,38 @@ class Imapami(object):
         imap.login(login, password)
         self.imap = imap
 
+    def get_uidnext(self):
+        """
+        Get the state (uidnext) for each inbox used in the configuration.
+        It gives the uid of the next message the will be added in the mbox.
+        We will only care about messages with a uid lower than this uidnext,
+        to avoid race conditions with a message arriving while we are in the
+        middle of rules processing.
+        """
+        self.logger.info('Getting inbox state...')
+        mboxes = [self.config["inbox"]] + [rule.inbox for rule in self.rules]
+        for m in mboxes:
+            if m is None:
+                continue
+            if self.uidnext.get(m, None) is not None:
+                continue
+            self.imap.select(m)
+            typ, dat = self.imap.status(m, "(UIDNEXT)")
+            if typ != 'OK':
+                raise ValueError("cannot get UIDNEXT: %s", typ)
+            match = re.match("[^ ]* \(UIDNEXT ([0-9]+)\)", dat[0])
+            if match is None:
+                raise ValueError("cannot match UIDNEXT: %s", typ)
+            self.uidnext[m] = int(match.groups()[0])
+        self.logger.info('Done: %r', self.uidnext)
+
     def process_rules(self):
         """
         Process the rules.
         """
         self.logger.info('Processing rules...')
-        inbox = self.config["inbox"]
         for rule in self.rules:
-            rule.process(self, inbox)
+            rule.process(self)
         self.logger.info('Done.')
 
     def close(self):
@@ -318,6 +344,7 @@ def main():
         sys.exit(0)
 
     p.connect()
+    p.get_uidnext()
     p.process_rules()
     p.close()
 
index 9efd6dd9b9d88b06782daa184be0939e62c33496..3adff91ccccbef00c3bffaf5762086b97baca501 100644 (file)
@@ -156,12 +156,6 @@ class ImapamiRule(object):
         :returns:
           A list of IMAP items
         """
-        # select the input mailbox
-        if self.inbox is not None:
-            ami.imap.select(self.inbox)
-        else:
-            ami.imap.select(inbox)
-
         # search messages matching conditions
         criteria = "(%s)" % self.get_criteria(ami)
         ami.logger.debug("processing rule %s, inbox %s, imap criteria %s",
@@ -173,6 +167,7 @@ class ImapamiRule(object):
             return
 
         item_list = items[0].split()
+        item_list = [i for i in item_list if int(i) < ami.uidnext[inbox]]
         ami.logger.debug("matching mails returned by server: %s", item_list)
         return item_list
 
@@ -202,15 +197,19 @@ class ImapamiRule(object):
         ami.logger.debug('get imap parts = %s', parts_str)
         return parts_str, parts
 
-    def process(self, ami, inbox):
+    def process(self, ami):
         """
         Process the rule.
 
         :arg Imapami ami:
           The Imapami object
-        :arg string inbox:
-          The default input mailbox directory.
         """
+        if self.inbox is not None:
+            inbox = self.inbox
+        else:
+            inbox = ami.config["inbox"]
+        ami.imap.select(inbox)
+
         # get the list of items (mails) matching the condition criteria
         item_list = self._search(ami, inbox)
         # determine what parts should be fetched