more style consistency

Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
This commit is contained in:
Nicolas Sebrecht 2015-01-08 17:13:33 +01:00
parent 594a286888
commit 0f40ca4799
14 changed files with 261 additions and 237 deletions

View File

@ -321,18 +321,18 @@ class SyncableAccount(Account):
if not remotefolder.sync_this: if not remotefolder.sync_this:
self.ui.debug('', "Not syncing filtered folder '%s'" self.ui.debug('', "Not syncing filtered folder '%s'"
"[%s]" % (remotefolder, remoterepos)) "[%s]"% (remotefolder, remoterepos))
continue # Ignore filtered folder continue # Ignore filtered folder
localfolder = self.get_local_folder(remotefolder) localfolder = self.get_local_folder(remotefolder)
if not localfolder.sync_this: if not localfolder.sync_this:
self.ui.debug('', "Not syncing filtered folder '%s'" self.ui.debug('', "Not syncing filtered folder '%s'"
"[%s]" % (localfolder, localfolder.repository)) "[%s]"% (localfolder, localfolder.repository))
continue # Ignore filtered folder continue # Ignore filtered folder
if not globals.options.singlethreading: if not globals.options.singlethreading:
thread = InstanceLimitedThread(\ thread = InstanceLimitedThread(\
instancename = 'FOLDER_' + self.remoterepos.getname(), instancename = 'FOLDER_' + self.remoterepos.getname(),
target = syncfolder, target = syncfolder,
name = "Folder %s [acc: %s]" % (remotefolder.getexplainedname(), self), name = "Folder %s [acc: %s]"% (remotefolder.getexplainedname(), self),
args = (self, remotefolder, quick)) args = (self, remotefolder, quick))
thread.start() thread.start()
folderthreads.append(thread) folderthreads.append(thread)
@ -385,6 +385,7 @@ def syncfolder(account, remotefolder, quick):
"""Synchronizes given remote folder for the specified account. """Synchronizes given remote folder for the specified account.
Filtered folders on the remote side will not invoke this function.""" Filtered folders on the remote side will not invoke this function."""
remoterepos = account.remoterepos remoterepos = account.remoterepos
localrepos = account.localrepos localrepos = account.localrepos
statusrepos = account.statusrepos statusrepos = account.statusrepos

View File

@ -19,13 +19,12 @@ import email
from email.Parser import Parser as MailParser from email.Parser import Parser as MailParser
def get_message_date(content, header='Date'): def get_message_date(content, header='Date'):
""" """Parses mail and returns resulting timestamp.
Parses mail and returns resulting timestamp.
:param header: the header to extract date from; :param header: the header to extract date from;
:returns: timestamp or `None` in the case of failure. :returns: timestamp or `None` in the case of failure.
""" """
message = MailParser().parsestr(content, True) message = MailParser().parsestr(content, True)
dateheader = message.get(header) dateheader = message.get(header)
# parsedate_tz returns a 10-tuple that can be passed to mktime_tz # parsedate_tz returns a 10-tuple that can be passed to mktime_tz

View File

@ -15,14 +15,15 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# 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 os.path
import re
from sys import exc_info
from offlineimap import threadutil, emailutil from offlineimap import threadutil, emailutil
from offlineimap import globals from offlineimap import globals
from offlineimap.ui import getglobalui from offlineimap.ui import getglobalui
from offlineimap.error import OfflineImapError from offlineimap.error import OfflineImapError
import offlineimap.accounts import offlineimap.accounts
import os.path
import re
from sys import exc_info
class BaseFolder(object): class BaseFolder(object):
@ -31,6 +32,7 @@ class BaseFolder(object):
:para name: Path & name of folder minus root or reference :para name: Path & name of folder minus root or reference
:para repository: Repository() in which the folder is. :para repository: Repository() in which the folder is.
""" """
self.ui = getglobalui() self.ui = getglobalui()
# Save original name for folderfilter operations # Save original name for folderfilter operations
self.ffilter_name = name self.ffilter_name = name
@ -56,15 +58,15 @@ class BaseFolder(object):
# Determine if we're running static or dynamic folder filtering # Determine if we're running static or dynamic folder filtering
# and check filtering status # and check filtering status
self._dynamic_folderfilter = \ self._dynamic_folderfilter = self.config.getdefaultboolean(
self.config.getdefaultboolean(repo, "dynamic_folderfilter", False) repo, "dynamic_folderfilter", False)
self._sync_this = repository.should_sync_folder(self.ffilter_name) self._sync_this = repository.should_sync_folder(self.ffilter_name)
if self._dynamic_folderfilter: if self._dynamic_folderfilter:
self.ui.debug('', "Running dynamic folder filtering on '%s'[%s]" \ self.ui.debug('', "Running dynamic folder filtering on '%s'[%s]"%
% (self.ffilter_name, repository)) (self.ffilter_name, repository))
elif not self._sync_this: elif not self._sync_this:
self.ui.debug('', "Filtering out '%s'[%s] due to folderfilter" \ self.ui.debug('', "Filtering out '%s'[%s] due to folderfilter"%
% (self.ffilter_name, repository)) (self.ffilter_name, repository))
# Passes for syncmessagesto # Passes for syncmessagesto
self.syncmessagesto_passes = [('copying messages' , self.__syncmessagesto_copy), self.syncmessagesto_passes = [('copying messages' , self.__syncmessagesto_copy),
@ -115,17 +117,20 @@ class BaseFolder(object):
:param statusfolder: keeps track of the last known folder state. :param statusfolder: keeps track of the last known folder state.
""" """
return True return True
def getcopyinstancelimit(self): def getcopyinstancelimit(self):
"""For threading folders, returns the instancelimitname for """For threading folders, returns the instancelimitname for
InstanceLimitedThreads.""" InstanceLimitedThreads."""
raise NotImplementedError raise NotImplementedError
def storesmessages(self): def storesmessages(self):
"""Should be true for any backend that actually saves message bodies. """Should be true for any backend that actually saves message bodies.
(Almost all of them). False for the LocalStatus backend. Saves (Almost all of them). False for the LocalStatus backend. Saves
us from having to slurp up messages just for localstatus purposes.""" us from having to slurp up messages just for localstatus purposes."""
return 1 return 1
def getvisiblename(self): def getvisiblename(self):
@ -143,14 +148,17 @@ class BaseFolder(object):
def getrepository(self): def getrepository(self):
"""Returns the repository object that this folder is within.""" """Returns the repository object that this folder is within."""
return self.repository return self.repository
def getroot(self): def getroot(self):
"""Returns the root of the folder, in a folder-specific fashion.""" """Returns the root of the folder, in a folder-specific fashion."""
return self.root return self.root
def getsep(self): def getsep(self):
"""Returns the separator for this folder type.""" """Returns the separator for this folder type."""
return self.sep return self.sep
def getfullname(self): def getfullname(self):
@ -160,7 +168,8 @@ class BaseFolder(object):
return self.getname() return self.getname()
def getfolderbasename(self): def getfolderbasename(self):
"""Return base file name of file to store Status/UID info in""" """Return base file name of file to store Status/UID info in."""
if not self.name: if not self.name:
basename = '.' basename = '.'
else: #avoid directory hierarchies and file names such as '/' else: #avoid directory hierarchies and file names such as '/'
@ -188,6 +197,7 @@ class BaseFolder(object):
def _getuidfilename(self): def _getuidfilename(self):
"""provides UIDVALIDITY cache filename for class internal purposes""" """provides UIDVALIDITY cache filename for class internal purposes"""
return os.path.join(self.repository.getuiddir(), return os.path.join(self.repository.getuiddir(),
self.getfolderbasename()) self.getfolderbasename())
@ -196,6 +206,7 @@ class BaseFolder(object):
:returns: UIDVALIDITY as (long) number or None, if None had been :returns: UIDVALIDITY as (long) number or None, if None had been
saved yet.""" saved yet."""
if hasattr(self, '_base_saved_uidvalidity'): if hasattr(self, '_base_saved_uidvalidity'):
return self._base_saved_uidvalidity return self._base_saved_uidvalidity
uidfilename = self._getuidfilename() uidfilename = self._getuidfilename()
@ -212,6 +223,7 @@ class BaseFolder(object):
This function is not threadsafe, so don't attempt to call it This function is not threadsafe, so don't attempt to call it
from concurrent threads.""" from concurrent threads."""
newval = self.get_uidvalidity() newval = self.get_uidvalidity()
uidfilename = self._getuidfilename() uidfilename = self._getuidfilename()
@ -225,45 +237,50 @@ class BaseFolder(object):
This function needs to be implemented by each Backend This function needs to be implemented by each Backend
:returns: UIDVALIDITY as a (long) number""" :returns: UIDVALIDITY as a (long) number"""
raise NotImplementedError raise NotImplementedError
def cachemessagelist(self): def cachemessagelist(self):
"""Reads the message list from disk or network and stores it in """Reads the message list from disk or network and stores it in
memory for later use. This list will not be re-read from disk or memory for later use. This list will not be re-read from disk or
memory unless this function is called again.""" memory unless this function is called again."""
raise NotImplementedError raise NotImplementedError
def getmessagelist(self): def getmessagelist(self):
"""Gets the current message list. """Gets the current message list.
You must call cachemessagelist() before calling this function!""" You must call cachemessagelist() before calling this function!"""
raise NotImplementedError raise NotImplementedError
def msglist_item_initializer(self, uid): def msglist_item_initializer(self, uid):
""" """Returns value for empty messagelist element with given UID.
Returns value for empty messagelist element with given UID.
This function must initialize all fields of messagelist item This function must initialize all fields of messagelist item
and must be called every time when one creates new messagelist and must be called every time when one creates new messagelist
entry to ensure that all fields that must be present are present. entry to ensure that all fields that must be present are present."""
"""
raise NotImplementedError raise NotImplementedError
def uidexists(self, uid): def uidexists(self, uid):
"""Returns True if uid exists""" """Returns True if uid exists"""
return uid in self.getmessagelist() return uid in self.getmessagelist()
def getmessageuidlist(self): def getmessageuidlist(self):
"""Gets a list of UIDs. """Gets a list of UIDs.
You may have to call cachemessagelist() before calling this function!""" You may have to call cachemessagelist() before calling this function!"""
return self.getmessagelist().keys() return self.getmessagelist().keys()
def getmessagecount(self): def getmessagecount(self):
"""Gets the number of messages.""" """Gets the number of messages."""
return len(self.getmessagelist()) return len(self.getmessagelist())
def getmessage(self, uid): def getmessage(self, uid):
"""Returns the content of the specified message.""" """Returns the content of the specified message."""
raise NotImplementedError raise NotImplementedError
def savemessage(self, uid, content, flags, rtime): def savemessage(self, uid, content, flags, rtime):
@ -286,20 +303,23 @@ class BaseFolder(object):
Note that savemessage() does not check against dryrun settings, Note that savemessage() does not check against dryrun settings,
so you need to ensure that savemessage is never called in a so you need to ensure that savemessage is never called in a
dryrun mode. dryrun mode."""
"""
raise NotImplementedError raise NotImplementedError
def getmessagetime(self, uid): def getmessagetime(self, uid):
"""Return the received time for the specified message.""" """Return the received time for the specified message."""
raise NotImplementedError raise NotImplementedError
def getmessagemtime(self, uid): def getmessagemtime(self, uid):
"""Returns the message modification time of the specified message.""" """Returns the message modification time of the specified message."""
raise NotImplementedError raise NotImplementedError
def getmessageflags(self, uid): def getmessageflags(self, uid):
"""Returns the flags for the specified message.""" """Returns the flags for the specified message."""
raise NotImplementedError raise NotImplementedError
def savemessageflags(self, uid, flags): def savemessageflags(self, uid, flags):
@ -308,6 +328,7 @@ class BaseFolder(object):
Note that this function does not check against dryrun settings, Note that this function does not check against dryrun settings,
so you need to ensure that it is never called in a so you need to ensure that it is never called in a
dryrun mode.""" dryrun mode."""
raise NotImplementedError raise NotImplementedError
def addmessageflags(self, uid, flags): def addmessageflags(self, uid, flags):
@ -319,14 +340,15 @@ class BaseFolder(object):
dryrun mode. dryrun mode.
:param flags: A set() of flags""" :param flags: A set() of flags"""
newflags = self.getmessageflags(uid) | flags newflags = self.getmessageflags(uid) | flags
self.savemessageflags(uid, newflags) self.savemessageflags(uid, newflags)
def addmessagesflags(self, uidlist, flags): def addmessagesflags(self, uidlist, flags):
""" """Note that this function does not check against dryrun settings,
Note that this function does not check against dryrun settings,
so you need to ensure that it is never called in a so you need to ensure that it is never called in a
dryrun mode.""" dryrun mode."""
for uid in uidlist: for uid in uidlist:
self.addmessageflags(uid, flags) self.addmessageflags(uid, flags)
@ -337,6 +359,7 @@ class BaseFolder(object):
Note that this function does not check against dryrun settings, Note that this function does not check against dryrun settings,
so you need to ensure that it is never called in a so you need to ensure that it is never called in a
dryrun mode.""" dryrun mode."""
newflags = self.getmessageflags(uid) - flags newflags = self.getmessageflags(uid) - flags
self.savemessageflags(uid, newflags) self.savemessageflags(uid, newflags)
@ -345,10 +368,10 @@ class BaseFolder(object):
Note that this function does not check against dryrun settings, Note that this function does not check against dryrun settings,
so you need to ensure that it is never called in a so you need to ensure that it is never called in a
dryrun mode.""" dryrun mode."""
for uid in uidlist: for uid in uidlist:
self.deletemessageflags(uid, flags) self.deletemessageflags(uid, flags)
def getmessagelabels(self, uid): def getmessagelabels(self, uid):
"""Returns the labels for the specified message.""" """Returns the labels for the specified message."""
raise NotImplementedError raise NotImplementedError
@ -359,6 +382,7 @@ class BaseFolder(object):
Note that this function does not check against dryrun settings, Note that this function does not check against dryrun settings,
so you need to ensure that it is never called in a so you need to ensure that it is never called in a
dryrun mode.""" dryrun mode."""
raise NotImplementedError raise NotImplementedError
def addmessagelabels(self, uid, labels): def addmessagelabels(self, uid, labels):
@ -370,14 +394,15 @@ class BaseFolder(object):
dryrun mode. dryrun mode.
:param labels: A set() of labels""" :param labels: A set() of labels"""
newlabels = self.getmessagelabels(uid) | labels newlabels = self.getmessagelabels(uid) | labels
self.savemessagelabels(uid, newlabels) self.savemessagelabels(uid, newlabels)
def addmessageslabels(self, uidlist, labels): def addmessageslabels(self, uidlist, labels):
""" """Note that this function does not check against dryrun settings,
Note that this function does not check against dryrun settings,
so you need to ensure that it is never called in a so you need to ensure that it is never called in a
dryrun mode.""" dryrun mode."""
for uid in uidlist: for uid in uidlist:
self.addmessagelabels(uid, labels) self.addmessagelabels(uid, labels)
@ -388,6 +413,7 @@ class BaseFolder(object):
Note that this function does not check against dryrun settings, Note that this function does not check against dryrun settings,
so you need to ensure that it is never called in a so you need to ensure that it is never called in a
dryrun mode.""" dryrun mode."""
newlabels = self.getmessagelabels(uid) - labels newlabels = self.getmessagelabels(uid) - labels
self.savemessagelabels(uid, newlabels) self.savemessagelabels(uid, newlabels)
@ -396,12 +422,12 @@ class BaseFolder(object):
Note that this function does not check against dryrun settings, Note that this function does not check against dryrun settings,
so you need to ensure that it is never called in a so you need to ensure that it is never called in a
dryrun mode.""" dryrun mode."""
for uid in uidlist: for uid in uidlist:
self.deletemessagelabels(uid, labels) self.deletemessagelabels(uid, labels)
def addmessageheader(self, content, linebreak, headername, headervalue): def addmessageheader(self, content, linebreak, headername, headervalue):
""" """Adds new header to the provided message.
Adds new header to the provided message.
WARNING: This function is a bit tricky, and modifying it in the wrong way, WARNING: This function is a bit tricky, and modifying it in the wrong way,
may easily lead to data-loss. may easily lead to data-loss.
@ -454,9 +480,9 @@ class BaseFolder(object):
This is the body\n This is the body\n
next line\n next line\n
""" """
self.ui.debug('',
'addmessageheader: called to add %s: %s' % (headername, self.ui.debug('', 'addmessageheader: called to add %s: %s'%
headervalue)) (headername, headervalue))
insertionpoint = content.find(linebreak * 2) insertionpoint = content.find(linebreak * 2)
if insertionpoint == -1: if insertionpoint == -1:
@ -490,24 +516,23 @@ class BaseFolder(object):
if content[0:len(linebreak)] != linebreak: if content[0:len(linebreak)] != linebreak:
suffix = suffix + linebreak suffix = suffix + linebreak
self.ui.debug('', 'addmessageheader: insertionpoint = %d' % insertionpoint) self.ui.debug('', 'addmessageheader: insertionpoint = %d'% insertionpoint)
headers = content[0:insertionpoint] headers = content[0:insertionpoint]
self.ui.debug('', 'addmessageheader: headers = %s' % repr(headers)) self.ui.debug('', 'addmessageheader: headers = %s'% repr(headers))
new_header = prefix + ("%s: %s" % (headername, headervalue)) + suffix new_header = prefix + ("%s: %s" % (headername, headervalue)) + suffix
self.ui.debug('', 'addmessageheader: new_header = ' + repr(new_header)) self.ui.debug('', 'addmessageheader: new_header = ' + repr(new_header))
return headers + new_header + content[insertionpoint:] return headers + new_header + content[insertionpoint:]
def __find_eoh(self, content): def __find_eoh(self, content):
""" """ Searches for the point where mail headers end.
Searches for the point where mail headers end.
Either double '\n', or end of string. Either double '\n', or end of string.
Arguments: Arguments:
- content: contents of the message to search in - content: contents of the message to search in
Returns: position of the first non-header byte. Returns: position of the first non-header byte.
""" """
eoh_cr = content.find('\n\n') eoh_cr = content.find('\n\n')
if eoh_cr == -1: if eoh_cr == -1:
eoh_cr = len(content) eoh_cr = len(content)
@ -516,8 +541,7 @@ class BaseFolder(object):
def getmessageheader(self, content, name): def getmessageheader(self, content, name):
""" """Searches for the first occurence of the given header and returns
Searches for the first occurence of the given header and returns
its value. Header name is case-insensitive. its value. Header name is case-insensitive.
Arguments: Arguments:
@ -525,13 +549,13 @@ class BaseFolder(object):
- name: name of the header to be searched - name: name of the header to be searched
Returns: header value or None if no such header was found Returns: header value or None if no such header was found
""" """
self.ui.debug('', 'getmessageheader: called to get %s' % name)
self.ui.debug('', 'getmessageheader: called to get %s'% name)
eoh = self.__find_eoh(content) eoh = self.__find_eoh(content)
self.ui.debug('', 'getmessageheader: eoh = %d' % eoh) self.ui.debug('', 'getmessageheader: eoh = %d'% eoh)
headers = content[0:eoh] headers = content[0:eoh]
self.ui.debug('', 'getmessageheader: headers = %s' % repr(headers)) self.ui.debug('', 'getmessageheader: headers = %s'% repr(headers))
m = re.search('^%s:(.*)$' % name, headers, flags = re.MULTILINE | re.IGNORECASE) m = re.search('^%s:(.*)$' % name, headers, flags = re.MULTILINE | re.IGNORECASE)
if m: if m:
@ -541,8 +565,7 @@ class BaseFolder(object):
def getmessageheaderlist(self, content, name): def getmessageheaderlist(self, content, name):
""" """Searches for the given header and returns a list of values for
Searches for the given header and returns a list of values for
that header. that header.
Arguments: Arguments:
@ -550,8 +573,8 @@ class BaseFolder(object):
- name: name of the header to be searched - name: name of the header to be searched
Returns: list of header values or emptylist if no such header was found Returns: list of header values or emptylist if no such header was found
""" """
self.ui.debug('', 'getmessageheaderlist: called to get %s' % name) self.ui.debug('', 'getmessageheaderlist: called to get %s' % name)
eoh = self.__find_eoh(content) eoh = self.__find_eoh(content)
self.ui.debug('', 'getmessageheaderlist: eoh = %d' % eoh) self.ui.debug('', 'getmessageheaderlist: eoh = %d' % eoh)
@ -562,27 +585,26 @@ class BaseFolder(object):
def deletemessageheaders(self, content, header_list): def deletemessageheaders(self, content, header_list):
""" """Deletes headers in the given list from the message content.
Deletes headers in the given list from the message content.
Arguments: Arguments:
- content: message itself - content: message itself
- header_list: list of headers to be deleted or just the header name - header_list: list of headers to be deleted or just the header name
We expect our message to have '\n' as line endings. We expect our message to have '\n' as line endings.
""" """
if type(header_list) != type([]): if type(header_list) != type([]):
header_list = [header_list] header_list = [header_list]
self.ui.debug('', 'deletemessageheaders: called to delete %s' % (header_list)) self.ui.debug('', 'deletemessageheaders: called to delete %s'% (header_list))
if not len(header_list): return content if not len(header_list): return content
eoh = self.__find_eoh(content) eoh = self.__find_eoh(content)
self.ui.debug('', 'deletemessageheaders: end of headers = %d' % eoh) self.ui.debug('', 'deletemessageheaders: end of headers = %d'% eoh)
headers = content[0:eoh] headers = content[0:eoh]
rest = content[eoh:] rest = content[eoh:]
self.ui.debug('', 'deletemessageheaders: headers = %s' % repr(headers)) self.ui.debug('', 'deletemessageheaders: headers = %s'% repr(headers))
new_headers = [] new_headers = []
for h in headers.split('\n'): for h in headers.split('\n'):
keep_it = True keep_it = True
@ -609,16 +631,14 @@ class BaseFolder(object):
raise NotImplementedError raise NotImplementedError
def deletemessage(self, uid): def deletemessage(self, uid):
""" """Note that this function does not check against dryrun settings,
Note that this function does not check against dryrun settings,
so you need to ensure that it is never called in a so you need to ensure that it is never called in a
dryrun mode.""" dryrun mode."""
raise NotImplementedError raise NotImplementedError
def deletemessages(self, uidlist): def deletemessages(self, uidlist):
""" """Note that this function does not check against dryrun settings,
Note that this function does not check against dryrun settings,
so you need to ensure that it is never called in a so you need to ensure that it is never called in a
dryrun mode.""" dryrun mode."""
@ -686,9 +706,8 @@ class BaseFolder(object):
self.deletemessage(uid) self.deletemessage(uid)
else: else:
raise OfflineImapError("Trying to save msg (uid %d) on folder " raise OfflineImapError("Trying to save msg (uid %d) on folder "
"%s returned invalid uid %d" % (uid, "%s returned invalid uid %d"% (uid, dstfolder.getvisiblename(),
dstfolder.getvisiblename(), new_uid), new_uid), OfflineImapError.ERROR.MESSAGE)
OfflineImapError.ERROR.MESSAGE)
except (KeyboardInterrupt): # bubble up CTRL-C except (KeyboardInterrupt): # bubble up CTRL-C
raise raise
except OfflineImapError as e: except OfflineImapError as e:
@ -697,8 +716,7 @@ class BaseFolder(object):
self.ui.error(e, exc_info()[2]) self.ui.error(e, exc_info()[2])
except Exception as e: except Exception as e:
self.ui.error(e, exc_info()[2], self.ui.error(e, exc_info()[2],
msg="Copying message %s [acc: %s]" %\ msg = "Copying message %s [acc: %s]"% (uid, self.accountname))
(uid, self.accountname))
raise #raise on unknown errors, so we can fix those raise #raise on unknown errors, so we can fix those
def __syncmessagesto_copy(self, dstfolder, statusfolder): def __syncmessagesto_copy(self, dstfolder, statusfolder):
@ -714,6 +732,7 @@ class BaseFolder(object):
This function checks and protects us from action in ryrun mode. This function checks and protects us from action in ryrun mode.
""" """
threads = [] threads = []
copylist = filter(lambda uid: not \ copylist = filter(lambda uid: not \
@ -754,6 +773,7 @@ class BaseFolder(object):
This function checks and protects us from action in ryrun mode. This function checks and protects us from action in ryrun mode.
""" """
deletelist = filter(lambda uid: uid>=0 \ deletelist = filter(lambda uid: uid>=0 \
and not self.uidexists(uid), and not self.uidexists(uid),
statusfolder.getmessageuidlist()) statusfolder.getmessageuidlist())
@ -776,6 +796,7 @@ class BaseFolder(object):
This function checks and protects us from action in ryrun mode. This function checks and protects us from action in ryrun mode.
""" """
# For each flag, we store a list of uids to which it should be # For each flag, we store a list of uids to which it should be
# added. Then, we can call addmessagesflags() to apply them in # added. Then, we can call addmessagesflags() to apply them in
# bulk, rather than one call per message. # bulk, rather than one call per message.
@ -854,8 +875,8 @@ class BaseFolder(object):
:param dstfolder: Folderinstance to sync the msgs to. :param dstfolder: Folderinstance to sync the msgs to.
:param statusfolder: LocalStatus instance to sync against. :param statusfolder: LocalStatus instance to sync against.
""" """
for (passdesc, action) in self.syncmessagesto_passes: for (passdesc, action) in self.syncmessagesto_passes:
# bail out on CTRL-C or SIGTERM # bail out on CTRL-C or SIGTERM
if offlineimap.accounts.Account.abort_NOW_signal.is_set(): if offlineimap.accounts.Account.abort_NOW_signal.is_set():
@ -883,6 +904,7 @@ class BaseFolder(object):
MailDirFolder('foo') == IMAPFolder('foo') --> False MailDirFolder('foo') == IMAPFolder('foo') --> False
MailDirFolder('foo') == MaildirFolder('foo') --> False MailDirFolder('foo') == MaildirFolder('foo') --> False
""" """
if isinstance(other, basestring): if isinstance(other, basestring):
return other == self.name return other == self.name
return id(self) == id(other) return id(self) == id(other)

View File

@ -20,6 +20,7 @@ import binascii
import re import re
import time import time
from sys import exc_info from sys import exc_info
from .Base import BaseFolder from .Base import BaseFolder
from offlineimap import imaputil, imaplibutil, emailutil, OfflineImapError from offlineimap import imaputil, imaplibutil, emailutil, OfflineImapError
from offlineimap import globals from offlineimap import globals
@ -54,7 +55,7 @@ class IMAPFolder(BaseFolder):
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]
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.
Prefer SELECT to EXAMINE if we can, since some servers Prefer SELECT to EXAMINE if we can, since some servers
@ -86,6 +87,7 @@ class IMAPFolder(BaseFolder):
UIDVALIDITY value will be cached on the first call. UIDVALIDITY value will be cached on the first call.
:returns: The UIDVALIDITY as (long) number.""" :returns: The UIDVALIDITY as (long) number."""
if hasattr(self, '_uidvalidity'): if hasattr(self, '_uidvalidity'):
# use cached value if existing # use cached value if existing
return self._uidvalidity return self._uidvalidity
@ -141,8 +143,7 @@ class IMAPFolder(BaseFolder):
def _msgs_to_fetch(self, imapobj): def _msgs_to_fetch(self, imapobj):
""" """Determines sequence numbers of messages to be fetched.
Determines sequence numbers of messages to be fetched.
Message sequence numbers (MSNs) are more easily compacted Message sequence numbers (MSNs) are more easily compacted
into ranges which makes transactions slightly faster. into ranges which makes transactions slightly faster.
@ -151,9 +152,8 @@ class IMAPFolder(BaseFolder):
- imapobj: instance of IMAPlib - imapobj: instance of IMAPlib
Returns: range(s) for messages or None if no messages Returns: range(s) for messages or None if no messages
are to be fetched. are to be fetched."""
"""
res_type, imapdata = imapobj.select(self.getfullname(), True, True) res_type, imapdata = imapobj.select(self.getfullname(), True, True)
if imapdata == [None] or imapdata[0] == '0': if imapdata == [None] or imapdata[0] == '0':
# Empty folder, no need to populate message list # Empty folder, no need to populate message list
@ -162,9 +162,9 @@ class IMAPFolder(BaseFolder):
# By default examine all messages in this folder # By default examine all messages in this folder
msgsToFetch = '1:*' msgsToFetch = '1:*'
maxage = self.config.getdefaultint("Account %s" % self.accountname, maxage = self.config.getdefaultint("Account %s"% self.accountname,
"maxage", -1) "maxage", -1)
maxsize = self.config.getdefaultint("Account %s" % self.accountname, maxsize = self.config.getdefaultint("Account %s"% self.accountname,
"maxsize", -1) "maxsize", -1)
# Build search condition # Build search condition
@ -193,7 +193,7 @@ class IMAPFolder(BaseFolder):
res_type, res_data = imapobj.search(None, search_cond) res_type, res_data = imapobj.search(None, search_cond)
if res_type != 'OK': if res_type != 'OK':
raise OfflineImapError("SEARCH in folder [%s]%s failed. " raise OfflineImapError("SEARCH in folder [%s]%s failed. "
"Search string was '%s'. Server responded '[%s] %s'" % ( "Search string was '%s'. Server responded '[%s] %s'"% (
self.getrepository(), self, search_cond, res_type, res_data), self.getrepository(), self, search_cond, res_type, res_data),
OfflineImapError.ERROR.FOLDER) OfflineImapError.ERROR.FOLDER)
@ -219,11 +219,11 @@ class IMAPFolder(BaseFolder):
# Get the flags and UIDs for these. single-quotes prevent # Get the flags and UIDs for these. single-quotes prevent
# imaplib2 from quoting the sequence. # imaplib2 from quoting the sequence.
res_type, response = imapobj.fetch("'%s'" % msgsToFetch, res_type, response = imapobj.fetch("'%s'"%
'(FLAGS UID)') msgsToFetch, '(FLAGS UID)')
if res_type != 'OK': if res_type != 'OK':
raise OfflineImapError("FETCHING UIDs in folder [%s]%s failed. " raise OfflineImapError("FETCHING UIDs in folder [%s]%s failed. "
"Server responded '[%s] %s'" % ( "Server responded '[%s] %s'"% (
self.getrepository(), self, self.getrepository(), self,
res_type, response), res_type, response),
OfflineImapError.ERROR.FOLDER) OfflineImapError.ERROR.FOLDER)
@ -238,7 +238,7 @@ class IMAPFolder(BaseFolder):
messagestr = messagestr.split(' ', 1)[1] messagestr = messagestr.split(' ', 1)[1]
options = imaputil.flags2hash(messagestr) options = imaputil.flags2hash(messagestr)
if not 'UID' in options: if not 'UID' in options:
self.ui.warn('No UID in message with options %s' %\ self.ui.warn('No UID in message with options %s'% \
str(options), str(options),
minor = 1) minor = 1)
else: else:
@ -255,16 +255,15 @@ class IMAPFolder(BaseFolder):
# Interface from BaseFolder # Interface from BaseFolder
def getmessage(self, uid): def getmessage(self, uid):
""" """Retrieve message with UID from the IMAP server (incl body)
Retrieve message with UID from the IMAP server (incl body)
After this function all CRLFs will be transformed to '\n'. After this function all CRLFs will be transformed to '\n'.
:returns: the message body or throws and OfflineImapError :returns: the message body or throws and OfflineImapError
(probably severity MESSAGE) if e.g. no message with (probably severity MESSAGE) if e.g. no message with
this UID could be found. this UID could be found.
""" """
imapobj = self.imapserver.acquireconnection() imapobj = self.imapserver.acquireconnection()
try: try:
data = self._fetch_from_imap(imapobj, str(uid), 2) data = self._fetch_from_imap(imapobj, str(uid), 2)
@ -281,7 +280,7 @@ class IMAPFolder(BaseFolder):
else: else:
dbg_output = data dbg_output = data
self.ui.debug('imap', "Returned object from fetching %d: '%s'" % self.ui.debug('imap', "Returned object from fetching %d: '%s'"%
(uid, dbg_output)) (uid, dbg_output))
return data return data
@ -307,6 +306,7 @@ class IMAPFolder(BaseFolder):
headername == 'X-OfflineIMAP' and headervalue will be a headername == 'X-OfflineIMAP' and headervalue will be a
random string random string
""" """
headername = 'X-OfflineIMAP' headername = 'X-OfflineIMAP'
# We need a random component too. If we ever upload the same # We need a random component too. If we ever upload the same
# mail twice (e.g. in different folders), we would still need to # mail twice (e.g. in different folders), we would still need to
@ -322,20 +322,20 @@ class IMAPFolder(BaseFolder):
def __savemessage_searchforheader(self, imapobj, headername, headervalue): def __savemessage_searchforheader(self, imapobj, headername, headervalue):
self.ui.debug('imap', '__savemessage_searchforheader called for %s: %s' % \ self.ui.debug('imap', '__savemessage_searchforheader called for %s: %s'% \
(headername, headervalue)) (headername, headervalue))
# Now find the UID it got. # Now find the UID it got.
headervalue = imapobj._quote(headervalue) headervalue = imapobj._quote(headervalue)
try: try:
matchinguids = imapobj.uid('search', 'HEADER', headername, headervalue)[1][0] matchinguids = imapobj.uid('search', 'HEADER', headername, headervalue)[1][0]
except imapobj.error as err: except imapobj.error as err:
# IMAP server doesn't implement search or had a problem. # IMAP server doesn't implement search or had a problem.
self.ui.debug('imap', "__savemessage_searchforheader: got IMAP error '%s' while attempting to UID SEARCH for message with header %s" % (err, headername)) self.ui.debug('imap', "__savemessage_searchforheader: got IMAP error '%s' while attempting to UID SEARCH for message with header %s"% (err, headername))
return 0 return 0
self.ui.debug('imap', '__savemessage_searchforheader got initial matchinguids: ' + repr(matchinguids)) self.ui.debug('imap', '__savemessage_searchforheader got initial matchinguids: ' + repr(matchinguids))
if matchinguids == '': if matchinguids == '':
self.ui.debug('imap', "__savemessage_searchforheader: UID SEARCH for message with header %s yielded no results" % headername) self.ui.debug('imap', "__savemessage_searchforheader: UID SEARCH for message with header %s yielded no results"% headername)
return 0 return 0
matchinguids = matchinguids.split(' ') matchinguids = matchinguids.split(' ')
@ -343,7 +343,7 @@ class IMAPFolder(BaseFolder):
repr(matchinguids)) repr(matchinguids))
if len(matchinguids) != 1 or matchinguids[0] == None: if len(matchinguids) != 1 or matchinguids[0] == None:
raise ValueError("While attempting to find UID for message with " raise ValueError("While attempting to find UID for message with "
"header %s, got wrong-sized matchinguids of %s" %\ "header %s, got wrong-sized matchinguids of %s"%\
(headername, str(matchinguids))) (headername, str(matchinguids)))
return long(matchinguids[0]) return long(matchinguids[0])
@ -368,9 +368,9 @@ class IMAPFolder(BaseFolder):
We need to locate the UID just after mail headers containing our We need to locate the UID just after mail headers containing our
X-OfflineIMAP line. X-OfflineIMAP line.
Returns UID when found, 0 when not found. Returns UID when found, 0 when not found."""
"""
self.ui.debug('imap', '__savemessage_fetchheaders called for %s: %s' % \ self.ui.debug('imap', '__savemessage_fetchheaders called for %s: %s'% \
(headername, headervalue)) (headername, headervalue))
# run "fetch X:* rfc822.header" # run "fetch X:* rfc822.header"
@ -381,7 +381,7 @@ class IMAPFolder(BaseFolder):
# ascending. # ascending.
if self.getmessagelist(): if self.getmessagelist():
start = 1+max(self.getmessagelist().keys()) start = 1 + max(self.getmessagelist().keys())
else: else:
# Folder was empty - start from 1 # Folder was empty - start from 1
start = 1 start = 1
@ -390,7 +390,7 @@ class IMAPFolder(BaseFolder):
# with the range X:*. So we use bytearray to stop imaplib from getting # with the range X:*. So we use bytearray to stop imaplib from getting
# in our way # in our way
result = imapobj.uid('FETCH', bytearray('%d:*' % start), 'rfc822.header') result = imapobj.uid('FETCH', bytearray('%d:*'% start), 'rfc822.header')
if result[0] != 'OK': if result[0] != 'OK':
raise OfflineImapError('Error fetching mail headers: ' + '. '.join(result[1]), raise OfflineImapError('Error fetching mail headers: ' + '. '.join(result[1]),
OfflineImapError.ERROR.MESSAGE) OfflineImapError.ERROR.MESSAGE)
@ -401,7 +401,7 @@ class IMAPFolder(BaseFolder):
for item in result: for item in result:
if found == 0 and type(item) == type( () ): if found == 0 and type(item) == type( () ):
# Walk just tuples # Walk just tuples
if re.search("(?:^|\\r|\\n)%s:\s*%s(?:\\r|\\n)" % (headername, headervalue), if re.search("(?:^|\\r|\\n)%s:\s*%s(?:\\r|\\n)"% (headername, headervalue),
item[1], flags=re.IGNORECASE): item[1], flags=re.IGNORECASE):
found = 1 found = 1
elif found == 1: elif found == 1:
@ -467,8 +467,8 @@ class IMAPFolder(BaseFolder):
# or something. Argh. It seems that Time2Internaldate # or something. Argh. It seems that Time2Internaldate
# will rause a ValueError if the year is 0102 but not 1902, # will rause a ValueError if the year is 0102 but not 1902,
# but some IMAP servers nonetheless choke on 1902. # but some IMAP servers nonetheless choke on 1902.
self.ui.debug('imap', "Message with invalid date %s. Server will use local time." \ self.ui.debug('imap', "Message with invalid date %s. "
% datetuple) "Server will use local time."% datetuple)
return None return None
#produce a string representation of datetuple that works as #produce a string representation of datetuple that works as
@ -507,6 +507,7 @@ class IMAPFolder(BaseFolder):
message is saved, but it's UID can not be found, it will message is saved, but it's UID can not be found, it will
return 0. If the message can't be written (folder is return 0. If the message can't be written (folder is
read-only for example) it will return -1.""" read-only for example) it will return -1."""
self.ui.savemessage('imap', uid, flags, self) self.ui.savemessage('imap', uid, flags, self)
# already have it, just save modified flags # already have it, just save modified flags
@ -543,17 +544,17 @@ class IMAPFolder(BaseFolder):
if not use_uidplus: if not use_uidplus:
# insert a random unique header that we can fetch later # insert a random unique header that we can fetch later
(headername, headervalue) = self.__generate_randomheader( (headername, headervalue) = self.__generate_randomheader(
content) content)
self.ui.debug('imap', 'savemessage: header is: %s: %s' %\ self.ui.debug('imap', 'savemessage: header is: %s: %s'%
(headername, headervalue)) (headername, headervalue))
content = self.addmessageheader(content, CRLF, headername, headervalue) content = self.addmessageheader(content, CRLF, headername, headervalue)
if len(content)>200: if len(content)>200:
dbg_output = "%s...%s" % (content[:150], content[-50:]) dbg_output = "%s...%s" % (content[:150], content[-50:])
else: else:
dbg_output = content dbg_output = content
self.ui.debug('imap', "savemessage: date: %s, content: '%s'" % self.ui.debug('imap', "savemessage: date: %s, content: '%s'"%
(date, dbg_output)) (date, dbg_output))
try: try:
# Select folder for append and make the box READ-WRITE # Select folder for append and make the box READ-WRITE
@ -566,9 +567,8 @@ class IMAPFolder(BaseFolder):
#Do the APPEND #Do the APPEND
try: try:
(typ, dat) = imapobj.append(self.getfullname(), (typ, dat) = imapobj.append(fullname,
imaputil.flagsmaildir2imap(flags), imaputil.flagsmaildir2imap(flags), date, content)
date, content)
# This should only catch 'NO' responses since append() # This should only catch 'NO' responses since append()
# will raise an exception for 'BAD' responses: # will raise an exception for 'BAD' responses:
if typ != 'OK': if typ != 'OK':
@ -580,7 +580,7 @@ class IMAPFolder(BaseFolder):
# and continue with the next account. # and continue with the next account.
msg = \ msg = \
"Saving msg (%s) in folder '%s', repository '%s' failed (abort). " \ "Saving msg (%s) in folder '%s', repository '%s' failed (abort). " \
"Server responded: %s %s\n" % \ "Server responded: %s %s\n"% \
(msg_id, self, self.getrepository(), typ, dat) (msg_id, self, self.getrepository(), typ, dat)
raise OfflineImapError(msg, OfflineImapError.ERROR.REPO) raise OfflineImapError(msg, OfflineImapError.ERROR.REPO)
retry_left = 0 # Mark as success retry_left = 0 # Mark as success
@ -592,7 +592,7 @@ class IMAPFolder(BaseFolder):
if not retry_left: if not retry_left:
raise OfflineImapError("Saving msg (%s) in folder '%s', " raise OfflineImapError("Saving msg (%s) in folder '%s', "
"repository '%s' failed (abort). Server responded: %s\n" "repository '%s' failed (abort). Server responded: %s\n"
"Message content was: %s" % "Message content was: %s"%
(msg_id, self, self.getrepository(), str(e), dbg_output), (msg_id, self, self.getrepository(), str(e), dbg_output),
OfflineImapError.ERROR.MESSAGE) OfflineImapError.ERROR.MESSAGE)
self.ui.error(e, exc_info()[2]) self.ui.error(e, exc_info()[2])
@ -604,8 +604,8 @@ class IMAPFolder(BaseFolder):
imapobj = None imapobj = None
raise OfflineImapError("Saving msg (%s) folder '%s', repo '%s'" raise OfflineImapError("Saving msg (%s) folder '%s', repo '%s'"
"failed (error). Server responded: %s\nMessage content was: " "failed (error). Server responded: %s\nMessage content was: "
"%s" % (msg_id, self, self.getrepository(), str(e), dbg_output), "%s"% (msg_id, self, self.getrepository(), str(e), dbg_output),
OfflineImapError.ERROR.MESSAGE) OfflineImapError.ERROR.MESSAGE)
# Checkpoint. Let it write out stuff, etc. Eg searches for # Checkpoint. Let it write out stuff, etc. Eg searches for
# just uploaded messages won't work if we don't do this. # just uploaded messages won't work if we don't do this.
(typ,dat) = imapobj.check() (typ,dat) = imapobj.check()
@ -622,24 +622,24 @@ class IMAPFolder(BaseFolder):
resp = imapobj._get_untagged_response('APPENDUID') resp = imapobj._get_untagged_response('APPENDUID')
if resp == [None] or resp is None: if resp == [None] or resp is None:
self.ui.warn("Server supports UIDPLUS but got no APPENDUID " self.ui.warn("Server supports UIDPLUS but got no APPENDUID "
"appending a message.") "appending a message.")
return 0 return 0
uid = long(resp[-1].split(' ')[1]) uid = long(resp[-1].split(' ')[1])
if uid == 0: if uid == 0:
self.ui.warn("savemessage: Server supports UIDPLUS, but" self.ui.warn("savemessage: Server supports UIDPLUS, but"
" we got no usable uid back. APPENDUID reponse was " " we got no usable uid back. APPENDUID reponse was "
"'%s'" % str(resp)) "'%s'"% str(resp))
else: else:
# we don't support UIDPLUS # we don't support UIDPLUS
uid = self.__savemessage_searchforheader(imapobj, headername, uid = self.__savemessage_searchforheader(imapobj, headername,
headervalue) headervalue)
# See docs for savemessage in Base.py for explanation # See docs for savemessage in Base.py for explanation
# of this and other return values # of this and other return values
if uid == 0: if uid == 0:
self.ui.debug('imap', 'savemessage: attempt to get new UID ' self.ui.debug('imap', 'savemessage: attempt to get new UID '
'UID failed. Search headers manually.') 'UID failed. Search headers manually.')
uid = self.__savemessage_fetchheaders(imapobj, headername, uid = self.__savemessage_fetchheaders(imapobj, headername,
headervalue) headervalue)
self.ui.warn('imap', "savemessage: Searching mails for new " self.ui.warn('imap', "savemessage: Searching mails for new "
"Message-ID failed. Could not determine new UID.") "Message-ID failed. Could not determine new UID.")
finally: finally:
@ -649,22 +649,21 @@ class IMAPFolder(BaseFolder):
self.messagelist[uid] = self.msglist_item_initializer(uid) self.messagelist[uid] = self.msglist_item_initializer(uid)
self.messagelist[uid]['flags'] = flags self.messagelist[uid]['flags'] = flags
self.ui.debug('imap', 'savemessage: returning new UID %d' % uid) self.ui.debug('imap', 'savemessage: returning new UID %d'% uid)
return uid return uid
def _fetch_from_imap(self, imapobj, uids, retry_num=1): def _fetch_from_imap(self, imapobj, uids, retry_num=1):
""" """Fetches data from IMAP server.
Fetches data from IMAP server.
Arguments: Arguments:
- imapobj: IMAPlib object - imapobj: IMAPlib object
- uids: message UIDS - uids: message UIDS
- retry_num: number of retries to make - retry_num: number of retries to make
Returns: data obtained by this query. Returns: data obtained by this query."""
"""
query = "(%s)" % (" ".join(self.imap_query)) query = "(%s)"% (" ".join(self.imap_query))
fails_left = retry_num # retry on dropped connection fails_left = retry_num # retry on dropped connection
while fails_left: while fails_left:
try: try:
@ -683,7 +682,7 @@ class IMAPFolder(BaseFolder):
#IMAP server says bad request or UID does not exist #IMAP server says bad request or UID does not exist
severity = OfflineImapError.ERROR.MESSAGE severity = OfflineImapError.ERROR.MESSAGE
reason = "IMAP server '%s' failed to fetch messages UID '%s'."\ reason = "IMAP server '%s' failed to fetch messages UID '%s'."\
"Server responded: %s %s" % (self.getrepository(), uids, "Server responded: %s %s"% (self.getrepository(), uids,
res_type, data) res_type, data)
if data == [None]: if data == [None]:
#IMAP server did not find a message with this UID #IMAP server did not find a message with this UID
@ -695,23 +694,21 @@ class IMAPFolder(BaseFolder):
def _store_to_imap(self, imapobj, uid, field, data): def _store_to_imap(self, imapobj, uid, field, data):
""" """Stores data to IMAP server
Stores data to IMAP server
Arguments: Arguments:
- imapobj: instance of IMAPlib to use - imapobj: instance of IMAPlib to use
- uid: message UID - uid: message UID
- field: field name to be stored/updated - field: field name to be stored/updated
- data: field contents - data: field contents
""" """
imapobj.select(self.getfullname()) imapobj.select(self.getfullname())
res_type, retdata = imapobj.uid('store', uid, field, data) res_type, retdata = imapobj.uid('store', uid, field, data)
if res_type != 'OK': if res_type != 'OK':
severity = OfflineImapError.ERROR.MESSAGE severity = OfflineImapError.ERROR.MESSAGE
reason = "IMAP server '%s' failed to store %s for message UID '%d'."\ reason = "IMAP server '%s' failed to store %s for message UID '%d'."\
"Server responded: %s %s" % (self.getrepository(), field, uid, "Server responded: %s %s"% (
res_type, retdata) self.getrepository(), field, uid, res_type, retdata)
raise OfflineImapError(reason, severity) raise OfflineImapError(reason, severity)
return retdata[0] return retdata[0]
@ -724,12 +721,11 @@ class IMAPFolder(BaseFolder):
dryrun mode.""" dryrun mode."""
imapobj = self.imapserver.acquireconnection() imapobj = self.imapserver.acquireconnection()
try: try:
result = self._store_to_imap(imapobj, str(uid), 'FLAGS', imaputil.flagsmaildir2imap(flags)) result = self._store_to_imap(imapobj, str(uid), 'FLAGS',
imaputil.flagsmaildir2imap(flags))
except imapobj.readonly: except imapobj.readonly:
self.ui.flagstoreadonly(self, [uid], flags) self.ui.flagstoreadonly(self, [uid], flags)
return return
finally: finally:
self.imapserver.releaseconnection(imapobj) self.imapserver.releaseconnection(imapobj)
@ -751,6 +747,7 @@ class IMAPFolder(BaseFolder):
"""This is here for the sake of UIDMaps.py -- deletemessages must """This is here for the sake of UIDMaps.py -- deletemessages must
add flags and get a converted UID, and if we don't have noconvert, add flags and get a converted UID, and if we don't have noconvert,
then UIDMaps will try to convert it twice.""" then UIDMaps will try to convert it twice."""
self.__addmessagesflags_noconvert(uidlist, flags) self.__addmessagesflags_noconvert(uidlist, flags)
# Interface from BaseFolder # Interface from BaseFolder
@ -770,9 +767,8 @@ class IMAPFolder(BaseFolder):
self.ui.flagstoreadonly(self, uidlist, flags) self.ui.flagstoreadonly(self, uidlist, flags)
return return
r = imapobj.uid('store', r = imapobj.uid('store',
imaputil.uid_sequence(uidlist), imaputil.uid_sequence(uidlist), operation + 'FLAGS',
operation + 'FLAGS', imaputil.flagsmaildir2imap(flags))
imaputil.flagsmaildir2imap(flags))
assert r[0] == 'OK', 'Error with store: ' + '. '.join(r[1]) assert r[0] == 'OK', 'Error with store: ' + '. '.join(r[1])
r = r[1] r = r[1]
finally: finally:
@ -818,9 +814,9 @@ class IMAPFolder(BaseFolder):
"""Change the message from existing uid to new_uid """Change the message from existing uid to new_uid
If the backend supports it. IMAP does not and will throw errors.""" If the backend supports it. IMAP does not and will throw errors."""
raise OfflineImapError('IMAP backend cannot change a messages UID from ' raise OfflineImapError('IMAP backend cannot change a messages UID from '
'%d to %d' % (uid, new_uid), '%d to %d'% (uid, new_uid), OfflineImapError.ERROR.MESSAGE)
OfflineImapError.ERROR.MESSAGE)
# Interface from BaseFolder # Interface from BaseFolder
def deletemessage(self, uid): def deletemessage(self, uid):

View File

@ -56,7 +56,7 @@ class IMAPServer:
self.preauth_tunnel = repos.getpreauthtunnel() self.preauth_tunnel = repos.getpreauthtunnel()
self.transport_tunnel = repos.gettransporttunnel() self.transport_tunnel = repos.gettransporttunnel()
if self.preauth_tunnel and self.transport_tunnel: if self.preauth_tunnel and self.transport_tunnel:
raise OfflineImapError('%s: ' % repos + \ raise OfflineImapError('%s: '% repos + \
'you must enable precisely one ' 'you must enable precisely one '
'type of tunnel (preauth or transport), ' 'type of tunnel (preauth or transport), '
'not both', OfflineImapError.ERROR.REPO) 'not both', OfflineImapError.ERROR.REPO)
@ -116,8 +116,9 @@ class IMAPServer:
# XXX: is this function used anywhere? # XXX: is this function used anywhere?
def getroot(self): def getroot(self):
"""Returns this server's folder root. Can only be called after one """Returns this server's folder root. Can only be called after one
or more calls to acquireconnection.""" or more calls to acquireconnection."""
return self.root return self.root
@ -126,6 +127,7 @@ class IMAPServer:
:param drop_conn: If True, the connection will be released and :param drop_conn: If True, the connection will be released and
not be reused. This can be used to indicate broken connections.""" not be reused. This can be used to indicate broken connections."""
if connection is None: return #noop on bad connection if connection is None: return #noop on bad connection
self.connectionlock.acquire() self.connectionlock.acquire()
self.assignedconnections.remove(connection) self.assignedconnections.remove(connection)
@ -139,25 +141,24 @@ class IMAPServer:
def __md5handler(self, response): def __md5handler(self, response):
challenge = response.strip() challenge = response.strip()
self.ui.debug('imap', '__md5handler: got challenge %s' % challenge) self.ui.debug('imap', '__md5handler: got challenge %s'% challenge)
passwd = self.__getpassword() passwd = self.__getpassword()
retval = self.username + ' ' + hmac.new(passwd, challenge).hexdigest() retval = self.username + ' ' + hmac.new(passwd, challenge).hexdigest()
self.ui.debug('imap', '__md5handler: returning %s' % retval) self.ui.debug('imap', '__md5handler: returning %s'% retval)
return retval return retval
def __loginauth(self, imapobj): def __loginauth(self, imapobj):
""" Basic authentication via LOGIN command """ """ Basic authentication via LOGIN command."""
self.ui.debug('imap', 'Attempting IMAP LOGIN authentication') self.ui.debug('imap', 'Attempting IMAP LOGIN authentication')
imapobj.login(self.username, self.__getpassword()) imapobj.login(self.username, self.__getpassword())
def __plainhandler(self, response): def __plainhandler(self, response):
""" """Implements SASL PLAIN authentication, RFC 4616,
Implements SASL PLAIN authentication, RFC 4616, http://tools.ietf.org/html/rfc4616"""
http://tools.ietf.org/html/rfc4616
"""
authc = self.username authc = self.username
passwd = self.__getpassword() passwd = self.__getpassword()
authz = '' authz = ''
@ -175,8 +176,8 @@ class IMAPServer:
try: try:
if self.gss_step == self.GSS_STATE_STEP: if self.gss_step == self.GSS_STATE_STEP:
if not self.gss_vc: if not self.gss_vc:
rc, self.gss_vc = kerberos.authGSSClientInit('imap@' + rc, self.gss_vc = kerberos.authGSSClientInit(
self.hostname) 'imap@' + self.hostname)
response = kerberos.authGSSClientResponse(self.gss_vc) response = kerberos.authGSSClientResponse(self.gss_vc)
rc = kerberos.authGSSClientStep(self.gss_vc, data) rc = kerberos.authGSSClientStep(self.gss_vc, data)
if rc != kerberos.AUTH_GSS_CONTINUE: if rc != kerberos.AUTH_GSS_CONTINUE:
@ -184,13 +185,13 @@ class IMAPServer:
elif self.gss_step == self.GSS_STATE_WRAP: elif self.gss_step == self.GSS_STATE_WRAP:
rc = kerberos.authGSSClientUnwrap(self.gss_vc, data) rc = kerberos.authGSSClientUnwrap(self.gss_vc, data)
response = kerberos.authGSSClientResponse(self.gss_vc) response = kerberos.authGSSClientResponse(self.gss_vc)
rc = kerberos.authGSSClientWrap(self.gss_vc, response, rc = kerberos.authGSSClientWrap(
self.username) self.gss_vc, response, self.username)
response = kerberos.authGSSClientResponse(self.gss_vc) response = kerberos.authGSSClientResponse(self.gss_vc)
except kerberos.GSSError as err: except kerberos.GSSError as err:
# Kerberos errored out on us, respond with None to cancel the # Kerberos errored out on us, respond with None to cancel the
# authentication # authentication
self.ui.debug('imap', '%s: %s' % (err[0][0], err[1][0])) self.ui.debug('imap', '%s: %s'% (err[0][0], err[1][0]))
return None return None
if not response: if not response:
@ -205,7 +206,7 @@ class IMAPServer:
imapobj.starttls() imapobj.starttls()
except imapobj.error as e: except imapobj.error as e:
raise OfflineImapError("Failed to start " raise OfflineImapError("Failed to start "
"TLS connection: %s" % str(e), "TLS connection: %s"% str(e),
OfflineImapError.ERROR.REPO) OfflineImapError.ERROR.REPO)
@ -266,8 +267,7 @@ class IMAPServer:
def __authn_helper(self, imapobj): def __authn_helper(self, imapobj):
""" """Authentication machinery for self.acquireconnection().
Authentication machinery for self.acquireconnection().
Raises OfflineImapError() of type ERROR.REPO when Raises OfflineImapError() of type ERROR.REPO when
there are either fatal problems or no authentications there are either fatal problems or no authentications
@ -275,9 +275,7 @@ class IMAPServer:
If any authentication method succeeds, routine should exit: If any authentication method succeeds, routine should exit:
warnings for failed methods are to be produced in the warnings for failed methods are to be produced in the
respective except blocks. respective except blocks."""
"""
# Authentication routines, hash keyed by method name # Authentication routines, hash keyed by method name
# with value that is a tuple with # with value that is a tuple with
@ -321,13 +319,13 @@ class IMAPServer:
continue continue
tried_to_authn = True tried_to_authn = True
self.ui.debug('imap', 'Attempting ' self.ui.debug('imap', u'Attempting '
'%s authentication' % m) '%s authentication'% m)
try: try:
if func(imapobj): if func(imapobj):
return return
except (imapobj.error, OfflineImapError) as e: except (imapobj.error, OfflineImapError) as e:
self.ui.warn('%s authentication failed: %s' % (m, e)) self.ui.warn('%s authentication failed: %s'% (m, e))
exc_stack.append((m, e)) exc_stack.append((m, e))
if len(exc_stack): if len(exc_stack):
@ -343,9 +341,9 @@ class IMAPServer:
lambda x: x[5:], filter(lambda x: x[0:5] == "AUTH=", lambda x: x[5:], filter(lambda x: x[0:5] == "AUTH=",
imapobj.capabilities) imapobj.capabilities)
)) ))
raise OfflineImapError("Repository %s: no supported " raise OfflineImapError(u"Repository %s: no supported "
"authentication mechanisms found; configured %s, " "authentication mechanisms found; configured %s, "
"server advertises %s" % (self.repos, "server advertises %s"% (self.repos,
", ".join(self.authmechs), methods), ", ".join(self.authmechs), methods),
OfflineImapError.ERROR.REPO) OfflineImapError.ERROR.REPO)
@ -383,9 +381,8 @@ class IMAPServer:
self.connectionlock.release() # Release until need to modify data self.connectionlock.release() # Release until need to modify data
""" Must be careful here that if we fail we should bail out gracefully # Must be careful here that if we fail we should bail out gracefully
and release locks / threads so that the next attempt can try... # and release locks / threads so that the next attempt can try...
"""
success = 0 success = 0
try: try:
while not success: while not success:
@ -441,7 +438,7 @@ class IMAPServer:
# No Folders were returned. This occurs, e.g. if the # No Folders were returned. This occurs, e.g. if the
# 'reference' prefix does not exist on the mail # 'reference' prefix does not exist on the mail
# server. Raise exception. # server. Raise exception.
err = "Server '%s' returned no folders in '%s'" % \ err = "Server '%s' returned no folders in '%s'"% \
(self.repos.getname(), self.reference) (self.repos.getname(), self.reference)
self.ui.warn(err) self.ui.warn(err)
raise Exception(err) raise Exception(err)
@ -458,6 +455,7 @@ class IMAPServer:
"""If we are here then we did not succeed in getting a """If we are here then we did not succeed in getting a
connection - we should clean up and then re-raise the connection - we should clean up and then re-raise the
error...""" error..."""
self.semaphore.release() self.semaphore.release()
severity = OfflineImapError.ERROR.REPO severity = OfflineImapError.ERROR.REPO
@ -489,7 +487,7 @@ class IMAPServer:
reason = "Connection to host '%s:%d' for repository '%s' was "\ reason = "Connection to host '%s:%d' for repository '%s' was "\
"refused. Make sure you have the right host and port "\ "refused. Make sure you have the right host and port "\
"configured and that you are actually able to access the "\ "configured and that you are actually able to access the "\
"network." % (self.hostname, self.port, self.repos) "network."% (self.hostname, self.port, self.repos)
raise OfflineImapError(reason, severity) raise OfflineImapError(reason, severity)
# Could not acquire connection to the remote; # Could not acquire connection to the remote;
# socket.error(last_error) raised # socket.error(last_error) raised
@ -503,13 +501,15 @@ class IMAPServer:
raise raise
def connectionwait(self): def connectionwait(self):
"""Waits until there is a connection available. Note that between """Waits until there is a connection available.
the time that a connection becomes available and the time it is
requested, another thread may have grabbed it. This function is Note that between the time that a connection becomes available and the
mainly present as a way to avoid spawning thousands of threads time it is requested, another thread may have grabbed it. This function
to copy messages, then have them all wait for 3 available connections. is mainly present as a way to avoid spawning thousands of threads to
It's OK if we have maxconnections + 1 or 2 threads, which is what copy messages, then have them all wait for 3 available connections.
this will help us do.""" It's OK if we have maxconnections + 1 or 2 threads, which is what this
will help us do."""
self.semaphore.acquire() self.semaphore.acquire()
self.semaphore.release() self.semaphore.release()
@ -533,11 +533,13 @@ class IMAPServer:
self.gssapi = False self.gssapi = False
def keepalive(self, timeout, event): def keepalive(self, timeout, event):
"""Sends a NOOP to each connection recorded. It will wait a maximum """Sends a NOOP to each connection recorded.
of timeout seconds between doing this, and will continue to do so
until the Event object as passed is true. This method is expected It will wait a maximum of timeout seconds between doing this, and will
to be invoked in a separate thread, which should be join()'d after continue to do so until the Event object as passed is true. This method
the event is set.""" is expected to be invoked in a separate thread, which should be join()'d
after the event is set."""
self.ui.debug('imap', 'keepalive thread started') self.ui.debug('imap', 'keepalive thread started')
while not event.isSet(): while not event.isSet():
self.connectionlock.acquire() self.connectionlock.acquire()
@ -547,7 +549,7 @@ class IMAPServer:
threads = [] threads = []
for i in range(numconnections): for i in range(numconnections):
self.ui.debug('imap', 'keepalive: processing connection %d of %d' % (i, numconnections)) self.ui.debug('imap', 'keepalive: processing connection %d of %d'% (i, numconnections))
if len(self.idlefolders) > i: if len(self.idlefolders) > i:
# IDLE thread # IDLE thread
idler = IdleThread(self, self.idlefolders[i]) idler = IdleThread(self, self.idlefolders[i])
@ -570,14 +572,14 @@ class IMAPServer:
return return
def __verifycert(self, cert, hostname): def __verifycert(self, cert, hostname):
'''Verify that cert (in socket.getpeercert() format) matches hostname. """Verify that cert (in socket.getpeercert() format) matches hostname.
CRLs are not handled.
CRLs are not handled.
Returns error message if any problems are found and None on success."""
Returns error message if any problems are found and None on success.
'''
errstr = "CA Cert verifying failed: " errstr = "CA Cert verifying failed: "
if not cert: if not cert:
return ('%s no certificate received' % errstr) return ('%s no certificate received'% errstr)
dnsname = hostname.lower() dnsname = hostname.lower()
certnames = [] certnames = []
@ -585,7 +587,7 @@ class IMAPServer:
notafter = cert.get('notAfter') notafter = cert.get('notAfter')
if notafter: if notafter:
if time.time() >= cert_time_to_seconds(notafter): if time.time() >= cert_time_to_seconds(notafter):
return '%s certificate expired %s' % (errstr, notafter) return '%s certificate expired %s'% (errstr, notafter)
# First read commonName # First read commonName
for s in cert.get('subject', []): for s in cert.get('subject', []):
@ -593,7 +595,7 @@ class IMAPServer:
if key == 'commonName': if key == 'commonName':
certnames.append(value.lower()) certnames.append(value.lower())
if len(certnames) == 0: if len(certnames) == 0:
return ('%s no commonName found in certificate' % errstr) return ('%s no commonName found in certificate'% errstr)
# Then read subjectAltName # Then read subjectAltName
for key, value in cert.get('subjectAltName', []): for key, value in cert.get('subjectAltName', []):
@ -606,7 +608,7 @@ class IMAPServer:
'.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]): '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
return None return None
return ('%s no matching domain name found in certificate' % errstr) return ('%s no matching domain name found in certificate'% errstr)
class IdleThread(object): class IdleThread(object):
@ -614,6 +616,7 @@ class IdleThread(object):
"""If invoked without 'folder', perform a NOOP and wait for """If invoked without 'folder', perform a NOOP and wait for
self.stop() to be called. If invoked with folder, switch to IDLE self.stop() to be called. If invoked with folder, switch to IDLE
mode and synchronize once we have a new message""" mode and synchronize once we have a new message"""
self.parent = parent self.parent = parent
self.folder = folder self.folder = folder
self.stop_sig = Event() self.stop_sig = Event()
@ -634,18 +637,18 @@ class IdleThread(object):
self.thread.join() self.thread.join()
def noop(self): def noop(self):
#TODO: AFAIK this is not optimal, we will send a NOOP on one # TODO: AFAIK this is not optimal, we will send a NOOP on one
#random connection (ie not enough to keep all connections # random connection (ie not enough to keep all connections
#open). In case we do the noop multiple times, we can well use # open). In case we do the noop multiple times, we can well use
#the same connection every time, as we get a random one. This # the same connection every time, as we get a random one. This
#function should IMHO send a noop on ALL available connections # function should IMHO send a noop on ALL available connections
#to the server. # to the server.
imapobj = self.parent.acquireconnection() imapobj = self.parent.acquireconnection()
try: try:
imapobj.noop() imapobj.noop()
except imapobj.abort: except imapobj.abort:
self.ui.warn('Attempting NOOP on dropped connection %s' % \ self.ui.warn('Attempting NOOP on dropped connection %s'%
imapobj.identifier) imapobj.identifier)
self.parent.releaseconnection(imapobj, True) self.parent.releaseconnection(imapobj, True)
imapobj = None imapobj = None
finally: finally:

View File

@ -121,8 +121,7 @@ def imapsplit(imapstring):
arg = arg.replace('\\', '\\\\') arg = arg.replace('\\', '\\\\')
arg = arg.replace('"', '\\"') arg = arg.replace('"', '\\"')
arg = '"%s"' % arg arg = '"%s"' % arg
__debug("imapsplit() non-string [%d]: Appending %s" %\ __debug("imapsplit() non-string [%d]: Appending %s"% (i, arg))
(i, arg))
retval.append(arg) retval.append(arg)
else: else:
# Even -- we have a string that ends with a literal # Even -- we have a string that ends with a literal
@ -131,8 +130,8 @@ def imapsplit(imapstring):
# Recursion to the rescue. # Recursion to the rescue.
arg = imapstring[i] arg = imapstring[i]
arg = re.sub('\{\d+\}$', '', arg) arg = re.sub('\{\d+\}$', '', arg)
__debug("imapsplit() non-string [%d]: Feeding %s to recursion" %\ __debug("imapsplit() non-string [%d]: Feeding %s to recursion"%\
(i, arg)) (i, arg))
retval.extend(imapsplit(arg)) retval.extend(imapsplit(arg))
__debug("imapsplit() non-string: returning %s" % str(retval)) __debug("imapsplit() non-string: returning %s" % str(retval))
return retval return retval

View File

@ -23,6 +23,7 @@ import signal
import socket import socket
import logging import logging
from optparse import OptionParser from optparse import OptionParser
import offlineimap import offlineimap
from offlineimap import accounts, threadutil, syncmaster from offlineimap import accounts, threadutil, syncmaster
from offlineimap import globals from offlineimap import globals
@ -180,7 +181,7 @@ class OfflineImap:
config = CustomConfigParser() config = CustomConfigParser()
if not os.path.exists(configfilename): if not os.path.exists(configfilename):
# TODO, initialize and make use of chosen ui for logging # TODO, initialize and make use of chosen ui for logging
logging.error(" *** Config file '%s' does not exist; aborting!" % logging.error(" *** Config file '%s' does not exist; aborting!"%
configfilename) configfilename)
sys.exit(1) sys.exit(1)
config.read(configfilename) config.read(configfilename)
@ -193,14 +194,14 @@ class OfflineImap:
options.singlethreading = True options.singlethreading = True
if os.path.exists(options.profiledir): if os.path.exists(options.profiledir):
# TODO, make use of chosen ui for logging # TODO, make use of chosen ui for logging
logging.warn("Profile mode: Directory '%s' already exists!" % logging.warn("Profile mode: Directory '%s' already exists!"%
options.profiledir) options.profiledir)
else: else:
os.mkdir(options.profiledir) os.mkdir(options.profiledir)
threadutil.ExitNotifyThread.set_profiledir(options.profiledir) threadutil.ExitNotifyThread.set_profiledir(options.profiledir)
# TODO, make use of chosen ui for logging # TODO, make use of chosen ui for logging
logging.warn("Profile mode: Potentially large data will be " logging.warn("Profile mode: Potentially large data will be "
"created in '%s'" % options.profiledir) "created in '%s'"% options.profiledir)
#override a config value #override a config value
if options.configoverride: if options.configoverride:
@ -234,8 +235,8 @@ class OfflineImap:
# create the ui class # create the ui class
self.ui = UI_LIST[ui_type.lower()](config) self.ui = UI_LIST[ui_type.lower()](config)
except KeyError: except KeyError:
logging.error("UI '%s' does not exist, choose one of: %s" % \ logging.error("UI '%s' does not exist, choose one of: %s"% \
(ui_type,', '.join(UI_LIST.keys()))) (ui_type, ', '.join(UI_LIST.keys())))
sys.exit(1) sys.exit(1)
setglobalui(self.ui) setglobalui(self.ui)
@ -331,13 +332,13 @@ class OfflineImap:
for account in activeaccounts: for account in activeaccounts:
if account not in allaccounts: if account not in allaccounts:
if len(allaccounts) == 0: if len(allaccounts) == 0:
errormsg = "The account '%s' does not exist because no"\ errormsg = "The account '%s' does not exist because no" \
" accounts are defined!" % account " accounts are defined!"% account
else: else:
errormsg = "The account '%s' does not exist. Valid ac"\ errormsg = "The account '%s' does not exist. Valid ac" \
"counts are: " % account "counts are: %s"% \
errormsg += ", ".join(allaccounts.keys()) (account, ", ".join(allaccounts.keys()))
self.ui.terminate(1, errormsg = errormsg) self.ui.terminate(1, errormsg=errormsg)
if account not in syncaccounts: if account not in syncaccounts:
syncaccounts.append(account) syncaccounts.append(account)

View File

@ -23,9 +23,7 @@ except:
pass pass
class LocalEval: class LocalEval:
"""Here is a powerfull but very dangerous option, of course. """Here is a powerfull but very dangerous option, of course."""
Assume source file to be ASCII encoded."""
def __init__(self, path=None): def __init__(self, path=None):
self.namespace = {} self.namespace = {}

View File

@ -167,6 +167,7 @@ class BaseRepository(CustomConfig.ConfigHelperMixin, object):
that forward and backward nametrans actually match up! that forward and backward nametrans actually match up!
Configuring nametrans on BOTH repositories therefore could lead Configuring nametrans on BOTH repositories therefore could lead
to infinite folder creation cycles.""" to infinite folder creation cycles."""
if not self.get_create_folders() and not dst_repo.get_create_folders(): if not self.get_create_folders() and not dst_repo.get_create_folders():
# quick exit if no folder creation is enabled on either side. # quick exit if no folder creation is enabled on either side.
return return

View File

@ -53,8 +53,8 @@ class LocalStatusRepository(BaseRepository):
self.LocalStatusFolderClass = self.backends[backend]['class'] self.LocalStatusFolderClass = self.backends[backend]['class']
else: else:
raise SyntaxWarning("Unknown status_backend '%s' for account '%s'" \ raise SyntaxWarning("Unknown status_backend '%s' for account '%s'"%
% (backend, self.account.name)) (backend, self.account.name))
def import_other_backend(self, folder): def import_other_backend(self, folder):
for bk, dic in self.backends.items(): for bk, dic in self.backends.items():
@ -68,8 +68,9 @@ class LocalStatusRepository(BaseRepository):
# if backend contains data, import it to folder. # if backend contains data, import it to folder.
if not folderbk.isnewfolder(): if not folderbk.isnewfolder():
self.ui._msg('Migrating LocalStatus cache from %s to %s ' % (bk, self._backend) + \ self.ui._msg('Migrating LocalStatus cache from %s to %s " \
'status folder for %s:%s' % (self.name, folder.name)) "status folder for %s:%s'%
(bk, self._backend, self.name, folder.name))
folderbk.cachemessagelist() folderbk.cachemessagelist()
folder.messagelist = folderbk.messagelist folder.messagelist = folderbk.messagelist

View File

@ -32,7 +32,7 @@ class MaildirRepository(BaseRepository):
self.root = self.getlocalroot() self.root = self.getlocalroot()
self.folders = None self.folders = None
self.ui = getglobalui() self.ui = getglobalui()
self.debug("MaildirRepository initialized, sep is " + repr(self.getsep())) self.debug("MaildirRepository initialized, sep is %s"% repr(self.getsep()))
self.folder_atimes = [] self.folder_atimes = []
# Create the top-level folder if it doesn't exist # Create the top-level folder if it doesn't exist
@ -101,12 +101,12 @@ class MaildirRepository(BaseRepository):
# If we're using hierarchical folders, it's possible that # If we're using hierarchical folders, it's possible that
# sub-folders may be created before higher-up ones. # sub-folders may be created before higher-up ones.
self.debug("makefolder: calling makedirs '%s'" % full_path) self.debug("makefolder: calling makedirs '%s'"% full_path)
try: try:
os.makedirs(full_path, 0o700) os.makedirs(full_path, 0o700)
except OSError as e: except OSError as e:
if e.errno == 17 and os.path.isdir(full_path): if e.errno == 17 and os.path.isdir(full_path):
self.debug("makefolder: '%s' already a directory" % foldername) self.debug("makefolder: '%s' already a directory"% foldername)
else: else:
raise raise
for subdir in ['cur', 'new', 'tmp']: for subdir in ['cur', 'new', 'tmp']:
@ -114,13 +114,13 @@ class MaildirRepository(BaseRepository):
os.mkdir(os.path.join(full_path, subdir), 0o700) os.mkdir(os.path.join(full_path, subdir), 0o700)
except OSError as e: except OSError as e:
if e.errno == 17 and os.path.isdir(full_path): if e.errno == 17 and os.path.isdir(full_path):
self.debug("makefolder: '%s' already has subdir %s" % self.debug("makefolder: '%s' already has subdir %s"%
(foldername, subdir)) (foldername, subdir))
else: else:
raise raise
def deletefolder(self, foldername): def deletefolder(self, foldername):
self.ui.warn("NOT YET IMPLEMENTED: DELETE FOLDER %s" % foldername) self.ui.warn("NOT YET IMPLEMENTED: DELETE FOLDER %s"% foldername)
def getfolder(self, foldername): def getfolder(self, foldername):
"""Return a Folder instance of this Maildir """Return a Folder instance of this Maildir
@ -128,6 +128,7 @@ class MaildirRepository(BaseRepository):
If necessary, scan and cache all foldernames to make sure that If necessary, scan and cache all foldernames to make sure that
we only return existing folders and that 2 calls with the same we only return existing folders and that 2 calls with the same
name will return the same object.""" name will return the same object."""
# getfolders() will scan and cache the values *if* necessary # getfolders() will scan and cache the values *if* necessary
folders = self.getfolders() folders = self.getfolders()
for f in folders: for f in folders:
@ -142,8 +143,9 @@ class MaildirRepository(BaseRepository):
:param root: (absolute) path to Maildir root :param root: (absolute) path to Maildir root
:param extension: (relative) subfolder to examine within root""" :param extension: (relative) subfolder to examine within root"""
self.debug("_GETFOLDERS_SCANDIR STARTING. root = %s, extension = %s" \
% (root, extension)) self.debug("_GETFOLDERS_SCANDIR STARTING. root = %s, extension = %s"%
(root, extension))
retval = [] retval = []
# Configure the full path to this repository -- "toppath" # Configure the full path to this repository -- "toppath"
@ -151,11 +153,11 @@ class MaildirRepository(BaseRepository):
toppath = os.path.join(root, extension) toppath = os.path.join(root, extension)
else: else:
toppath = root toppath = root
self.debug(" toppath = %s" % toppath) self.debug(" toppath = %s"% toppath)
# Iterate over directories in top & top itself. # Iterate over directories in top & top itself.
for dirname in os.listdir(toppath) + ['']: for dirname in os.listdir(toppath) + ['']:
self.debug(" dirname = %s" % dirname) self.debug(" dirname = %s"% dirname)
if dirname == '' and extension is not None: if dirname == '' and extension is not None:
self.debug(' skip this entry (already scanned)') self.debug(' skip this entry (already scanned)')
continue continue
@ -178,7 +180,7 @@ class MaildirRepository(BaseRepository):
os.path.isdir(os.path.join(fullname, 'new')) and os.path.isdir(os.path.join(fullname, 'new')) and
os.path.isdir(os.path.join(fullname, 'tmp'))): os.path.isdir(os.path.join(fullname, 'tmp'))):
# This directory has maildir stuff -- process # This directory has maildir stuff -- process
self.debug(" This is maildir folder '%s'." % foldername) self.debug(" This is maildir folder '%s'."% foldername)
if self.getconfboolean('restoreatime', False): if self.getconfboolean('restoreatime', False):
self._append_folder_atimes(foldername) self._append_folder_atimes(foldername)
fd = self.getfoldertype()(self.root, foldername, fd = self.getfoldertype()(self.root, foldername,
@ -188,7 +190,7 @@ class MaildirRepository(BaseRepository):
if self.getsep() == '/' and dirname != '': if self.getsep() == '/' and dirname != '':
# Recursively check sub-directories for folders too. # Recursively check sub-directories for folders too.
retval.extend(self._getfolders_scandir(root, foldername)) retval.extend(self._getfolders_scandir(root, foldername))
self.debug("_GETFOLDERS_SCANDIR RETURNING %s" % \ self.debug("_GETFOLDERS_SCANDIR RETURNING %s"% \
repr([x.getname() for x in retval])) repr([x.getname() for x in retval]))
return retval return retval

View File

@ -23,7 +23,7 @@ from offlineimap import banner
from offlineimap.ui.UIBase import UIBase from offlineimap.ui.UIBase import UIBase
class TTYFormatter(logging.Formatter): class TTYFormatter(logging.Formatter):
"""Specific Formatter that adds thread information to the log output""" """Specific Formatter that adds thread information to the log output."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
#super() doesn't work in py2.6 as 'logging' uses old-style class #super() doesn't work in py2.6 as 'logging' uses old-style class
@ -31,7 +31,8 @@ class TTYFormatter(logging.Formatter):
self._last_log_thread = None self._last_log_thread = None
def format(self, record): def format(self, record):
"""Override format to add thread information""" """Override format to add thread information."""
#super() doesn't work in py2.6 as 'logging' uses old-style class #super() doesn't work in py2.6 as 'logging' uses old-style class
log_str = logging.Formatter.format(self, record) log_str = logging.Formatter.format(self, record)
# If msg comes from a different thread than our last, prepend # If msg comes from a different thread than our last, prepend
@ -44,7 +45,7 @@ class TTYFormatter(logging.Formatter):
self._last_log_thread = t_name self._last_log_thread = t_name
log_str = "%s:\n %s" % (t_name, log_str) log_str = "%s:\n %s" % (t_name, log_str)
else: else:
log_str = " %s" % log_str log_str = " %s"% log_str
return log_str return log_str
@ -69,15 +70,15 @@ class TTYUI(UIBase):
return ch return ch
def isusable(self): def isusable(self):
"""TTYUI is reported as usable when invoked on a terminal""" """TTYUI is reported as usable when invoked on a terminal."""
return sys.stdout.isatty() and sys.stdin.isatty() return sys.stdout.isatty() and sys.stdin.isatty()
def getpass(self, accountname, config, errmsg=None): def getpass(self, accountname, config, errmsg=None):
"""TTYUI backend is capable of querying the password""" """TTYUI backend is capable of querying the password."""
if errmsg: if errmsg:
self.warn("%s: %s" % (accountname, errmsg)) self.warn("%s: %s"% (accountname, errmsg))
self._log_con_handler.acquire() # lock the console output self._log_con_handler.acquire() # lock the console output
try: try:
return getpass("Enter password for account '%s': " % accountname) return getpass("Enter password for account '%s': " % accountname)
@ -87,7 +88,7 @@ class TTYUI(UIBase):
def mainException(self): def mainException(self):
if isinstance(sys.exc_info()[1], KeyboardInterrupt): if isinstance(sys.exc_info()[1], KeyboardInterrupt):
self.logger.warn("Timer interrupted at user request; program " self.logger.warn("Timer interrupted at user request; program "
"terminating.\n") "terminating.\n")
self.terminate() self.terminate()
else: else:
UIBase.mainException(self) UIBase.mainException(self)
@ -100,12 +101,11 @@ class TTYUI(UIBase):
This implementation in UIBase does not support this, but some This implementation in UIBase does not support this, but some
implementations return 0 for successful sleep and 1 for an implementations return 0 for successful sleep and 1 for an
'abort', ie a request to sync immediately. 'abort', ie a request to sync immediately."""
"""
if sleepsecs > 0: if sleepsecs > 0:
if remainingsecs//60 != (remainingsecs-sleepsecs)//60: if remainingsecs//60 != (remainingsecs-sleepsecs)//60:
self.logger.info("Next refresh in %.1f minutes" % ( self.logger.info("Next refresh in %.1f minutes" % (
remainingsecs/60.0)) remainingsecs/60.0))
time.sleep(sleepsecs) time.sleep(sleepsecs)
return 0 return 0

View File

@ -393,11 +393,11 @@ class UIBase(object):
uid, dest, num, num_to_set, ", ".join(labels))) uid, dest, num, num_to_set, ", ".join(labels)))
def collectingdata(self, uidlist, source): def collectingdata(self, uidlist, source):
if uidlist: if uidlist:
self.logger.info("Collecting data from %d messages on %s" % ( self.logger.info("Collecting data from %d messages on %s"% (
len(uidlist), source)) len(uidlist), source))
else: else:
self.logger.info("Collecting data from messages on %s" % source) self.logger.info("Collecting data from messages on %s"% source)
def serverdiagnostics(self, repository, type): def serverdiagnostics(self, repository, type):
"""Connect to repository and output useful information for debugging.""" """Connect to repository and output useful information for debugging."""

View File

@ -28,7 +28,8 @@ class DebuggingLock:
def acquire(self, blocking = 1): def acquire(self, blocking = 1):
self.print_tb("Acquire lock") self.print_tb("Acquire lock")
self.lock.acquire(blocking) self.lock.acquire(blocking)
self.logmsg("===== %s: Thread %s acquired lock\n" % (self.name, currentThread().getName())) self.logmsg("===== %s: Thread %s acquired lock\n"%
(self.name, currentThread().getName()))
def release(self): def release(self):
self.print_tb("Release lock") self.print_tb("Release lock")
@ -41,7 +42,7 @@ class DebuggingLock:
loglock.release() loglock.release()
def print_tb(self, msg): def print_tb(self, msg):
self.logmsg(".... %s: Thread %s attempting to %s\n" % \ self.logmsg(".... %s: Thread %s attempting to %s\n"% \
(self.name, currentThread().getName(), msg) + \ (self.name, currentThread().getName(), msg) + \
"\n".join(traceback.format_list(traceback.extract_stack()))) "\n".join(traceback.format_list(traceback.extract_stack())))