learn to not download UIDs defined by the user

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

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
This commit is contained in:
Nicolas Sebrecht 2016-06-28 23:58:03 +02:00
parent f0096391fc
commit 29e06a60f9
8 changed files with 68 additions and 11 deletions

View File

@ -1187,6 +1187,17 @@ remoteuser = u"username"
#"cvlc --play-and-stop --play-and-exit /path/to/sound/file.mp3 > /dev/null 2>&1") #"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] [Repository GmailExample]
# A repository using Gmail's IMAP interface. # A repository using Gmail's IMAP interface.

View File

@ -45,6 +45,7 @@ class BaseFolder(object):
if self.name == 'INBOX': if self.name == 'INBOX':
self.newmail_hook = repository.newmail_hook self.newmail_hook = repository.newmail_hook
self.have_newmail = False self.have_newmail = False
self.copy_ignoreUIDs = None # List of UIDs to ignore.
self.repository = repository self.repository = repository
self.visiblename = repository.nametrans(name) self.visiblename = repository.nametrans(name)
# In case the visiblename becomes '.' or '/' (top-level) we use # In case the visiblename becomes '.' or '/' (top-level) we use
@ -870,6 +871,13 @@ class BaseFolder(object):
if not statusfolder.uidexists(uid)] if not statusfolder.uidexists(uid)]
num_to_copy = len(copylist) 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: if num_to_copy > 0 and self.repository.account.dryrun:
self.ui.info( self.ui.info(
"[DRYRUN] Copy {0} messages from {1}[{2}] to {3}".format( "[DRYRUN] Copy {0} messages from {1}[{2}] to {3}".format(

View File

@ -58,6 +58,10 @@ class IMAPFolder(BaseFolder):
fh_conf = self.repository.account.getconf('filterheaders', '') fh_conf = self.repository.account.getconf('filterheaders', '')
self.filterheaders = [h for h in re.split(r'\s*,\s*', fh_conf) if h] 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): def __selectro(self, imapobj, force=False):
"""Select this folder when we do not need write access. """Select this folder when we do not need write access.

View File

@ -40,6 +40,8 @@ class IMAPRepository(BaseRepository):
self._oauth2_request_url = None self._oauth2_request_url = None
self.imapserver = imapserver.IMAPServer(self) self.imapserver = imapserver.IMAPServer(self)
self.folders = None self.folders = None
self.copy_ignore_eval = None
# Only set the newmail_hook in an IMAP repository. # Only set the newmail_hook in an IMAP repository.
if self.config.has_option(self.getsection(), 'newmail_hook'): if self.config.has_option(self.getsection(), 'newmail_hook'):
self.newmail_hook = self.localeval.eval( self.newmail_hook = self.localeval.eval(
@ -75,6 +77,19 @@ class IMAPRepository(BaseRepository):
def dropconnections(self): def dropconnections(self):
self.imapserver.close() 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): def getholdconnectionopen(self):
if self.getidlefolders(): if self.getidlefolders():
return 1 return 1

View File

@ -1,5 +1,5 @@
# Curses-based interfaces # 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 # 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 # 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') self.gettf().setcolor('blue')
super(Blinkenlights, self).syncingmessages(*args) super(Blinkenlights, self).syncingmessages(*args)
def ignorecopyingmessage(self, *args):
self.gettf().setcolor('red')
super(Blinkenlights, self).ignorecopyingmessage(*args)
def copyingmessage(self, *args): def copyingmessage(self, *args):
self.gettf().setcolor('orange') self.gettf().setcolor('orange')
super(Blinkenlights, self).copyingmessage(*args) super(Blinkenlights, self).copyingmessage(*args)

View File

@ -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 # 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 # it under the terms of the GNU General Public License as published by
@ -21,10 +21,11 @@ import sys
import time import time
import logging import logging
from threading import currentThread from threading import currentThread
from offlineimap.ui.UIBase import UIBase
import offlineimap
protocol = '7.0.0' import offlineimap
from offlineimap.ui.UIBase import UIBase
protocol = '7.1.0'
class MachineLogFormatter(logging.Formatter): class MachineLogFormatter(logging.Formatter):
"""urlencodes any outputted line, to avoid multi-line output""" """urlencodes any outputted line, to avoid multi-line output"""
@ -125,6 +126,11 @@ class MachineUI(UIBase):
(s.getnicename(sr), sf.getname(), s.getnicename(dr), (s.getnicename(sr), sf.getname(), s.getnicename(dr),
df.getname())) 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): def copyingmessage(s, uid, num, num_to_copy, srcfolder, destfolder):
s._printData(s.logger.info, 'copyingmessage', "%d\n%s\n%s\n%s[%s]"% s._printData(s.logger.info, 'copyingmessage', "%d\n%s\n%s\n%s[%s]"%
(uid, s.getnicename(srcfolder), srcfolder.getname(), (uid, s.getnicename(srcfolder), srcfolder.getname(),

View File

@ -1,5 +1,5 @@
# Noninteractive UI # 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 # 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 # 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 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import logging import logging
from offlineimap.ui.UIBase import UIBase
import offlineimap import offlineimap
from offlineimap.ui.UIBase import UIBase
class Basic(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): def __init__(self, config, loglevel = logging.INFO):
return super(Basic, self).__init__(config, loglevel) return super(Basic, self).__init__(config, loglevel)

View File

@ -1,5 +1,5 @@
# UI base class # 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 # 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 # it under the terms of the GNU General Public License as published by
@ -27,8 +27,9 @@ try:
except ImportError: # python3 except ImportError: # python3
from queue import Queue from queue import Queue
from collections import deque from collections import deque
from offlineimap.error import OfflineImapError
import offlineimap import offlineimap
from offlineimap.error import OfflineImapError
debugtypes = {'':'Other offlineimap related sync messages', debugtypes = {'':'Other offlineimap related sync messages',
'imap': 'IMAP protocol debugging', 'imap': 'IMAP protocol debugging',
@ -385,6 +386,12 @@ class UIBase(object):
self.getnicename(sr), srcfolder, self.getnicename(sr), srcfolder,
self.getnicename(dr), dstfolder)) 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): def copyingmessage(self, uid, num, num_to_copy, src, destfolder):
"""Output a log line stating which message we copy.""" """Output a log line stating which message we copy."""