[PATCH 4/4] learn to not download UIDs defined by the user

Nicolas Sebrecht nicolas.s-dev at laposte.net
Tue Jun 28 23:04:47 BST 2016


Allow users to workaround offending emails that offlineimap can't download.

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev at laposte.net>
---
 offlineimap.conf                 | 11 +++++++++++
 offlineimap/folder/Base.py       |  8 ++++++++
 offlineimap/folder/IMAP.py       |  4 ++++
 offlineimap/repository/IMAP.py   | 15 +++++++++++++++
 offlineimap/ui/Curses.py         |  6 +++++-
 offlineimap/ui/Machine.py        | 12 +++++++++---
 offlineimap/ui/Noninteractive.py |  8 +++++---
 offlineimap/ui/UIBase.py         | 13 ++++++++++---
 8 files changed, 67 insertions(+), 10 deletions(-)

diff --git a/offlineimap.conf b/offlineimap.conf
index e2e6115..14d14df 100644
--- a/offlineimap.conf
+++ b/offlineimap.conf
@@ -1187,6 +1187,17 @@ remoteuser = u"username"
 #"cvlc --play-and-stop --play-and-exit /path/to/sound/file.mp3 > /dev/null 2>&1")
 
 
+# This option stands in the [Repository RemoteExample] section.
+#
+# If offlineiamp is having troubles to download some UIDS, it's possible to get
+# them ignored in a list.
+#
+# The function must return the list of UIDs to ignore, None otherwise. It is
+# passed the folder name.
+#
+#copy_ignore_eval = lambda foldername: {'INBOX': [2, 3, 4]}.get(foldername)
+
+
 [Repository GmailExample]
 
 # A repository using Gmail's IMAP interface.
diff --git a/offlineimap/folder/Base.py b/offlineimap/folder/Base.py
index 98f78f8..e467f23 100644
--- a/offlineimap/folder/Base.py
+++ b/offlineimap/folder/Base.py
@@ -45,6 +45,7 @@ class BaseFolder(object):
         if self.name == 'INBOX':
             self.newmail_hook = repository.newmail_hook
         self.have_newmail = False
+        self.copy_ignoreUIDs = None # List of UIDs to ignore.
         self.repository = repository
         self.visiblename = repository.nametrans(name)
         # In case the visiblename becomes '.' or '/' (top-level) we use
@@ -870,6 +871,13 @@ class BaseFolder(object):
             if not statusfolder.uidexists(uid)]
         num_to_copy = len(copylist)
 
+        # Honor 'copy_ignore_eval' configuration option.
+        if self.copy_ignoreUIDs is not None:
+            for uid in self.copy_ignoreUIDs:
+                if uid in copylist:
+                    copylist.remove(uid)
+                    self.ui.ignorecopyingmessage(uid, self, dstfolder)
+
         if num_to_copy > 0 and self.repository.account.dryrun:
             self.ui.info(
                 "[DRYRUN] Copy {0} messages from {1}[{2}] to {3}".format(
diff --git a/offlineimap/folder/IMAP.py b/offlineimap/folder/IMAP.py
index 917c17f..a8cd1d2 100644
--- a/offlineimap/folder/IMAP.py
+++ b/offlineimap/folder/IMAP.py
@@ -58,6 +58,10 @@ class IMAPFolder(BaseFolder):
         fh_conf = self.repository.account.getconf('filterheaders', '')
         self.filterheaders = [h for h in re.split(r'\s*,\s*', fh_conf) if h]
 
+        # self.copy_ignoreUIDs is used by BaseFolder.
+        self.copy_ignoreUIDs = repository.get_copy_ignore_UIDs(
+            self.getvisiblename())
+
 
     def __selectro(self, imapobj, force=False):
         """Select this folder when we do not need write access.
diff --git a/offlineimap/repository/IMAP.py b/offlineimap/repository/IMAP.py
index fdd9955..7ed9301 100644
--- a/offlineimap/repository/IMAP.py
+++ b/offlineimap/repository/IMAP.py
@@ -40,6 +40,8 @@ class IMAPRepository(BaseRepository):
         self._oauth2_request_url = None
         self.imapserver = imapserver.IMAPServer(self)
         self.folders = None
+        self.copy_ignore_eval = None
+
         # Only set the newmail_hook in an IMAP repository.
         if self.config.has_option(self.getsection(), 'newmail_hook'):
             self.newmail_hook = self.localeval.eval(
@@ -75,6 +77,19 @@ class IMAPRepository(BaseRepository):
     def dropconnections(self):
         self.imapserver.close()
 
+    def get_copy_ignore_UIDs(self, foldername):
+        """Return a list of UIDs to not copy for this foldername."""
+
+        if self.copy_ignore_eval is None:
+            if self.config.has_option(self.getsection(),
+                                     'copy_ignore_eval'):
+                self.copy_ignore_eval = self.localeval.eval(
+                        self.getconf('copy_ignore_eval'))
+            else:
+                self.copy_ignore_eval = lambda x: None
+
+        return self.copy_ignore_eval(foldername)
+
     def getholdconnectionopen(self):
         if self.getidlefolders():
             return 1
diff --git a/offlineimap/ui/Curses.py b/offlineimap/ui/Curses.py
index d5b148d..dfefb7d 100644
--- a/offlineimap/ui/Curses.py
+++ b/offlineimap/ui/Curses.py
@@ -1,5 +1,5 @@
 # Curses-based interfaces
-# Copyright (C) 2003-2015 John Goerzen & contributors
+# Copyright (C) 2003-2016 John Goerzen & contributors.
 #
 #    This program is free software; you can redistribute it and/or modify
 #    it under the terms of the GNU General Public License as published by
@@ -442,6 +442,10 @@ class Blinkenlights(UIBase, CursesUtil):
         self.gettf().setcolor('blue')
         super(Blinkenlights, self).syncingmessages(*args)
 
+    def ignorecopyingmessage(self, *args):
+        self.gettf().setcolor('red')
+        super(Blinkenlights, self).ignorecopyingmessage(*args)
+
     def copyingmessage(self, *args):
         self.gettf().setcolor('orange')
         super(Blinkenlights, self).copyingmessage(*args)
diff --git a/offlineimap/ui/Machine.py b/offlineimap/ui/Machine.py
index dc650c3..06de17b 100644
--- a/offlineimap/ui/Machine.py
+++ b/offlineimap/ui/Machine.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007-2015 John Goerzen & contributors
+# Copyright (C) 2007-2016 John Goerzen & contributors.
 #
 #    This program is free software; you can redistribute it and/or modify
 #    it under the terms of the GNU General Public License as published by
@@ -21,10 +21,11 @@ import sys
 import time
 import logging
 from threading import currentThread
-from offlineimap.ui.UIBase import UIBase
+
 import offlineimap
+from offlineimap.ui.UIBase import UIBase
 
-protocol = '7.0.0'
+protocol = '7.1.0'
 
 class MachineLogFormatter(logging.Formatter):
     """urlencodes any outputted line, to avoid multi-line output"""
@@ -125,6 +126,11 @@ class MachineUI(UIBase):
                 (s.getnicename(sr), sf.getname(), s.getnicename(dr),
                  df.getname()))
 
+    def ignorecopyingmessage(s, uid, srcfolder, destfolder):
+        s._printData(s.logger.info, 'ignorecopyingmessage', "%d\n%s\n%s\n%s[%s]"%
+                (uid, s.getnicename(srcfolder), srcfolder.getname(),
+                 s.getnicename(destfolder), destfolder))
+
     def copyingmessage(s, uid, num, num_to_copy, srcfolder, destfolder):
         s._printData(s.logger.info, 'copyingmessage', "%d\n%s\n%s\n%s[%s]"%
                 (uid, s.getnicename(srcfolder), srcfolder.getname(),
diff --git a/offlineimap/ui/Noninteractive.py b/offlineimap/ui/Noninteractive.py
index 0e23a93..e7ef096 100644
--- a/offlineimap/ui/Noninteractive.py
+++ b/offlineimap/ui/Noninteractive.py
@@ -1,5 +1,5 @@
 # Noninteractive UI
-# Copyright (C) 2002-2012 John Goerzen & contributors
+# Copyright (C) 2002-2016 John Goerzen & contributors.
 #
 #    This program is free software; you can redistribute it and/or modify
 #    it under the terms of the GNU General Public License as published by
@@ -16,11 +16,13 @@
 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 
 import logging
-from offlineimap.ui.UIBase import UIBase
+
 import offlineimap
+from offlineimap.ui.UIBase import UIBase
 
 class Basic(UIBase):
-    """'Basic' simply sets log level to INFO"""
+    """'Basic' simply sets log level to INFO."""
+
     def __init__(self, config, loglevel = logging.INFO):
         return super(Basic, self).__init__(config, loglevel)
 
diff --git a/offlineimap/ui/UIBase.py b/offlineimap/ui/UIBase.py
index 769a008..2aa7ca8 100644
--- a/offlineimap/ui/UIBase.py
+++ b/offlineimap/ui/UIBase.py
@@ -1,5 +1,5 @@
 # UI base class
-# Copyright (C) 2002-2016 John Goerzen & contributors
+# Copyright (C) 2002-2016 John Goerzen & contributors.
 #
 #    This program is free software; you can redistribute it and/or modify
 #    it under the terms of the GNU General Public License as published by
@@ -24,11 +24,12 @@ import traceback
 import threading
 try:
     from Queue import Queue
-except ImportError: #python3
+except ImportError: # python3
     from queue import Queue
 from collections import deque
-from offlineimap.error import OfflineImapError
+
 import offlineimap
+from offlineimap.error import OfflineImapError
 
 debugtypes = {'':'Other offlineimap related sync messages',
               'imap': 'IMAP protocol debugging',
@@ -385,6 +386,12 @@ class UIBase(object):
                 self.getnicename(sr), srcfolder,
                 self.getnicename(dr), dstfolder))
 
+    def ignorecopyingmessage(self, uid, src, destfolder):
+        """Output a log line stating which message is ignored."""
+
+        self.logger.info("IGNORED: Copy message UID %s %s:%s -> %s"% (
+                uid, src.repository, src, destfolder.repository))
+
     def copyingmessage(self, uid, num, num_to_copy, src, destfolder):
         """Output a log line stating which message we copy."""
 
-- 
2.7.4





More information about the OfflineIMAP-project mailing list