more consistent style
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
This commit is contained in:
parent
11a28fb0cb
commit
61021260cb
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2003-2012 John Goerzen & contributors
|
# Copyright (C) 2003-2015 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
|
||||||
@ -14,21 +14,20 @@
|
|||||||
# 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
|
||||||
|
import re
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from ConfigParser import SafeConfigParser, Error
|
from ConfigParser import SafeConfigParser, Error
|
||||||
except ImportError: #python3
|
except ImportError: #python3
|
||||||
from configparser import SafeConfigParser, Error
|
from configparser import SafeConfigParser, Error
|
||||||
from offlineimap.localeval import LocalEval
|
from offlineimap.localeval import LocalEval
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
class CustomConfigParser(SafeConfigParser):
|
class CustomConfigParser(SafeConfigParser):
|
||||||
def getdefault(self, section, option, default, *args, **kwargs):
|
def getdefault(self, section, option, default, *args, **kwargs):
|
||||||
"""
|
"""Same as config.get, but returns the value of `default`
|
||||||
Same as config.get, but returns the value of `default`
|
if there is no such option specified."""
|
||||||
if there is no such option specified.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.has_option(section, option):
|
if self.has_option(section, option):
|
||||||
return self.get(*(section, option) + args, **kwargs)
|
return self.get(*(section, option) + args, **kwargs)
|
||||||
else:
|
else:
|
||||||
@ -36,45 +35,37 @@ class CustomConfigParser(SafeConfigParser):
|
|||||||
|
|
||||||
|
|
||||||
def getdefaultint(self, section, option, default, *args, **kwargs):
|
def getdefaultint(self, section, option, default, *args, **kwargs):
|
||||||
"""
|
"""Same as config.getint, but returns the value of `default`
|
||||||
Same as config.getint, but returns the value of `default`
|
if there is no such option specified."""
|
||||||
if there is no such option specified.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.has_option(section, option):
|
if self.has_option(section, option):
|
||||||
return self.getint (*(section, option) + args, **kwargs)
|
return self.getint(*(section, option) + args, **kwargs)
|
||||||
else:
|
else:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
def getdefaultfloat(self, section, option, default, *args, **kwargs):
|
def getdefaultfloat(self, section, option, default, *args, **kwargs):
|
||||||
"""
|
"""Same as config.getfloat, but returns the value of `default`
|
||||||
Same as config.getfloat, but returns the value of `default`
|
if there is no such option specified."""
|
||||||
if there is no such option specified.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.has_option(section, option):
|
if self.has_option(section, option):
|
||||||
return self.getfloat(*(section, option) + args, **kwargs)
|
return self.getfloat(*(section, option) + args, **kwargs)
|
||||||
else:
|
else:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def getdefaultboolean(self, section, option, default, *args, **kwargs):
|
def getdefaultboolean(self, section, option, default, *args, **kwargs):
|
||||||
"""
|
"""Same as config.getboolean, but returns the value of `default`
|
||||||
Same as config.getboolean, but returns the value of `default`
|
if there is no such option specified."""
|
||||||
if there is no such option specified.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.has_option(section, option):
|
if self.has_option(section, option):
|
||||||
return self.getboolean(*(section, option) + args, **kwargs)
|
return self.getboolean(*(section, option) + args, **kwargs)
|
||||||
else:
|
else:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def getlist(self, section, option, separator_re):
|
def getlist(self, section, option, separator_re):
|
||||||
"""
|
"""Parses option as the list of values separated
|
||||||
Parses option as the list of values separated
|
by the given regexp."""
|
||||||
by the given regexp.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
val = self.get(section, option).strip()
|
val = self.get(section, option).strip()
|
||||||
return re.split(separator_re, val)
|
return re.split(separator_re, val)
|
||||||
@ -83,11 +74,9 @@ class CustomConfigParser(SafeConfigParser):
|
|||||||
(separator_re, e))
|
(separator_re, e))
|
||||||
|
|
||||||
def getdefaultlist(self, section, option, default, separator_re):
|
def getdefaultlist(self, section, option, default, separator_re):
|
||||||
"""
|
"""Same as getlist, but returns the value of `default`
|
||||||
Same as getlist, but returns the value of `default`
|
if there is no such option specified."""
|
||||||
if there is no such option specified.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.has_option(section, option):
|
if self.has_option(section, option):
|
||||||
return self.getlist(*(section, option, separator_re))
|
return self.getlist(*(section, option, separator_re))
|
||||||
else:
|
else:
|
||||||
@ -104,40 +93,48 @@ class CustomConfigParser(SafeConfigParser):
|
|||||||
def getlocaleval(self):
|
def getlocaleval(self):
|
||||||
xforms = [os.path.expanduser, os.path.expandvars]
|
xforms = [os.path.expanduser, os.path.expandvars]
|
||||||
if self.has_option("general", "pythonfile"):
|
if self.has_option("general", "pythonfile"):
|
||||||
path = self.apply_xforms(self.get("general", "pythonfile"), xforms)
|
if globals.options.use_unicode:
|
||||||
|
path = uni.fsEncode(self.get("general", "pythonfile"),
|
||||||
|
exception_msg="cannot convert character for pythonfile")
|
||||||
|
else:
|
||||||
|
path = self.get("general", "pythonfile")
|
||||||
|
path = self.apply_xforms(path, xforms)
|
||||||
else:
|
else:
|
||||||
path = None
|
path = None
|
||||||
return LocalEval(path)
|
return LocalEval(path)
|
||||||
|
|
||||||
def getsectionlist(self, key):
|
def getsectionlist(self, key):
|
||||||
"""
|
"""Returns a list of sections that start with (str) key + " ".
|
||||||
Returns a list of sections that start with key + " ".
|
|
||||||
|
|
||||||
That is, if key is "Account", returns all section names that
|
That is, if key is "Account", returns all section names that
|
||||||
start with "Account ", but strips off the "Account ".
|
start with "Account ", but strips off the "Account ".
|
||||||
|
|
||||||
For instance, for "Account Test", returns "Test".
|
For instance, for "Account Test", returns "Test"."""
|
||||||
|
|
||||||
"""
|
|
||||||
key = key + ' '
|
key = key + ' '
|
||||||
|
if globals.options.use_unicode:
|
||||||
|
sections = []
|
||||||
|
for section in self.sections():
|
||||||
|
sections.append(uni.uni2str(section, exception_msg=
|
||||||
|
"non ASCII character in section %s"% section))
|
||||||
|
return [x[len(key):] for x in sections \
|
||||||
|
if x.startswith(key)]
|
||||||
|
else:
|
||||||
return [x[len(key):] for x in self.sections() \
|
return [x[len(key):] for x in self.sections() \
|
||||||
if x.startswith(key)]
|
if x.startswith(key)]
|
||||||
|
|
||||||
def set_if_not_exists(self, section, option, value):
|
def set_if_not_exists(self, section, option, value):
|
||||||
"""
|
"""Set a value if it does not exist yet.
|
||||||
Set a value if it does not exist yet
|
|
||||||
|
|
||||||
This allows to set default if the user has not explicitly
|
This allows to set default if the user has not explicitly
|
||||||
configured anything.
|
configured anything."""
|
||||||
|
|
||||||
"""
|
|
||||||
if not self.has_option(section, option):
|
if not self.has_option(section, option):
|
||||||
self.set(section, option, value)
|
self.set(section, option, value)
|
||||||
|
|
||||||
|
|
||||||
def apply_xforms(self, string, transforms):
|
def apply_xforms(self, string, transforms):
|
||||||
"""
|
"""Applies set of transformations to a string.
|
||||||
Applies set of transformations to a string.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- string: source string; if None, then no processing will
|
- string: source string; if None, then no processing will
|
||||||
@ -145,9 +142,8 @@ class CustomConfigParser(SafeConfigParser):
|
|||||||
- transforms: iterable that returns transformation function
|
- transforms: iterable that returns transformation function
|
||||||
on each turn.
|
on each turn.
|
||||||
|
|
||||||
Returns transformed string.
|
Returns transformed string."""
|
||||||
|
|
||||||
"""
|
|
||||||
if string == None:
|
if string == None:
|
||||||
return None
|
return None
|
||||||
for f in transforms:
|
for f in transforms:
|
||||||
@ -157,21 +153,18 @@ class CustomConfigParser(SafeConfigParser):
|
|||||||
|
|
||||||
|
|
||||||
def CustomConfigDefault():
|
def CustomConfigDefault():
|
||||||
"""
|
"""Just a constant that won't occur anywhere else.
|
||||||
Just a constant that won't occur anywhere else.
|
|
||||||
|
|
||||||
This allows us to differentiate if the user has passed in any
|
This allows us to differentiate if the user has passed in any
|
||||||
default value to the getconf* functions in ConfigHelperMixin
|
default value to the getconf* functions in ConfigHelperMixin
|
||||||
derived classes.
|
derived classes."""
|
||||||
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigHelperMixin:
|
class ConfigHelperMixin:
|
||||||
"""
|
"""Allow comfortable retrieving of config values pertaining
|
||||||
Allow comfortable retrieving of config values pertaining
|
|
||||||
to a section.
|
to a section.
|
||||||
|
|
||||||
If a class inherits from cls:`ConfigHelperMixin`, it needs
|
If a class inherits from cls:`ConfigHelperMixin`, it needs
|
||||||
@ -181,13 +174,10 @@ class ConfigHelperMixin:
|
|||||||
the section to look up).
|
the section to look up).
|
||||||
All calls to getconf* will then return the configuration values
|
All calls to getconf* will then return the configuration values
|
||||||
for the CustomConfigParser object in the specific section.
|
for the CustomConfigParser object in the specific section.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _confighelper_runner(self, option, default, defaultfunc, mainfunc, *args):
|
def _confighelper_runner(self, option, default, defaultfunc, mainfunc, *args):
|
||||||
"""
|
"""Returns configuration or default value for option
|
||||||
Returns configuration or default value for option
|
|
||||||
that contains in section identified by getsection().
|
that contains in section identified by getsection().
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@ -201,8 +191,8 @@ class ConfigHelperMixin:
|
|||||||
- defaultfunc and mainfunc: processing helpers.
|
- defaultfunc and mainfunc: processing helpers.
|
||||||
- args: additional trailing arguments that will be passed
|
- args: additional trailing arguments that will be passed
|
||||||
to all processing helpers.
|
to all processing helpers.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
lst = [self.getsection(), option]
|
lst = [self.getsection(), option]
|
||||||
if default == CustomConfigDefault:
|
if default == CustomConfigDefault:
|
||||||
return mainfunc(*(lst + list(args)))
|
return mainfunc(*(lst + list(args)))
|
||||||
@ -210,50 +200,43 @@ class ConfigHelperMixin:
|
|||||||
lst.append(default)
|
lst.append(default)
|
||||||
return defaultfunc(*(lst + list(args)))
|
return defaultfunc(*(lst + list(args)))
|
||||||
|
|
||||||
|
|
||||||
def getconfig(self):
|
def getconfig(self):
|
||||||
"""
|
"""Returns CustomConfigParser object that we will use
|
||||||
Returns CustomConfigParser object that we will use
|
|
||||||
for all our actions.
|
for all our actions.
|
||||||
|
|
||||||
Must be overriden in all classes that use this mix-in.
|
Must be overriden in all classes that use this mix-in."""
|
||||||
|
|
||||||
"""
|
|
||||||
raise NotImplementedError("ConfigHelperMixin.getconfig() "
|
raise NotImplementedError("ConfigHelperMixin.getconfig() "
|
||||||
"is to be overriden")
|
"is to be overriden")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def getsection(self):
|
def getsection(self):
|
||||||
"""
|
"""Returns name of configuration section in which our
|
||||||
Returns name of configuration section in which our
|
|
||||||
class keeps its configuration.
|
class keeps its configuration.
|
||||||
|
|
||||||
Must be overriden in all classes that use this mix-in.
|
Must be overriden in all classes that use this mix-in."""
|
||||||
|
|
||||||
"""
|
|
||||||
raise NotImplementedError("ConfigHelperMixin.getsection() "
|
raise NotImplementedError("ConfigHelperMixin.getsection() "
|
||||||
"is to be overriden")
|
"is to be overriden")
|
||||||
|
|
||||||
|
|
||||||
def getconf(self, option, default = CustomConfigDefault):
|
def getconf(self, option, default = CustomConfigDefault):
|
||||||
"""
|
"""Retrieves string from the configuration.
|
||||||
Retrieves string from the configuration.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- option: option name whose value is to be retrieved;
|
- option: option name whose value is to be retrieved;
|
||||||
- default: default return value if no such option
|
- default: default return value if no such option
|
||||||
exists.
|
exists.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self._confighelper_runner(option, default,
|
return self._confighelper_runner(option, default,
|
||||||
self.getconfig().getdefault,
|
self.getconfig().getdefault,
|
||||||
self.getconfig().get)
|
self.getconfig().get)
|
||||||
|
|
||||||
|
|
||||||
def getconf_xform(self, option, xforms, default = CustomConfigDefault):
|
def getconf_xform(self, option, xforms, default = CustomConfigDefault):
|
||||||
"""
|
"""Retrieves string from the configuration transforming the result.
|
||||||
Retrieves string from the configuration transforming the result.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- option: option name whose value is to be retrieved;
|
- option: option name whose value is to be retrieved;
|
||||||
@ -262,22 +245,21 @@ class ConfigHelperMixin:
|
|||||||
both retrieved and default one;
|
both retrieved and default one;
|
||||||
- default: default value for string if no such option
|
- default: default value for string if no such option
|
||||||
exists.
|
exists.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
value = self.getconf(option, default)
|
value = self.getconf(option, default)
|
||||||
return self.getconfig().apply_xforms(value, xforms)
|
return self.getconfig().apply_xforms(value, xforms)
|
||||||
|
|
||||||
|
|
||||||
def getconfboolean(self, option, default = CustomConfigDefault):
|
def getconfboolean(self, option, default = CustomConfigDefault):
|
||||||
"""
|
"""Retrieves boolean value from the configuration.
|
||||||
Retrieves boolean value from the configuration.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- option: option name whose value is to be retrieved;
|
- option: option name whose value is to be retrieved;
|
||||||
- default: default return value if no such option
|
- default: default return value if no such option
|
||||||
exists.
|
exists.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self._confighelper_runner(option, default,
|
return self._confighelper_runner(option, default,
|
||||||
self.getconfig().getdefaultboolean,
|
self.getconfig().getdefaultboolean,
|
||||||
self.getconfig().getboolean)
|
self.getconfig().getboolean)
|
||||||
@ -293,21 +275,21 @@ class ConfigHelperMixin:
|
|||||||
exists.
|
exists.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self._confighelper_runner(option, default,
|
return self._confighelper_runner(option, default,
|
||||||
self.getconfig().getdefaultint,
|
self.getconfig().getdefaultint,
|
||||||
self.getconfig().getint)
|
self.getconfig().getint)
|
||||||
|
|
||||||
|
|
||||||
def getconffloat(self, option, default = CustomConfigDefault):
|
def getconffloat(self, option, default = CustomConfigDefault):
|
||||||
"""
|
"""Retrieves floating-point value from the configuration.
|
||||||
Retrieves floating-point value from the configuration.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- option: option name whose value is to be retrieved;
|
- option: option name whose value is to be retrieved;
|
||||||
- default: default return value if no such option
|
- default: default return value if no such option
|
||||||
exists.
|
exists.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self._confighelper_runner(option, default,
|
return self._confighelper_runner(option, default,
|
||||||
self.getconfig().getdefaultfloat,
|
self.getconfig().getdefaultfloat,
|
||||||
self.getconfig().getfloat)
|
self.getconfig().getfloat)
|
||||||
@ -315,8 +297,7 @@ class ConfigHelperMixin:
|
|||||||
|
|
||||||
def getconflist(self, option, separator_re,
|
def getconflist(self, option, separator_re,
|
||||||
default = CustomConfigDefault):
|
default = CustomConfigDefault):
|
||||||
"""
|
"""Retrieves strings from the configuration and splits it
|
||||||
Retrieves strings from the configuration and splits it
|
|
||||||
into the list of strings.
|
into the list of strings.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@ -325,8 +306,8 @@ class ConfigHelperMixin:
|
|||||||
to be used for split operation;
|
to be used for split operation;
|
||||||
- default: default return value if no such option
|
- default: default return value if no such option
|
||||||
exists.
|
exists.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self._confighelper_runner(option, default,
|
return self._confighelper_runner(option, default,
|
||||||
self.getconfig().getdefaultlist,
|
self.getconfig().getdefaultlist,
|
||||||
self.getconfig().getlist, separator_re)
|
self.getconfig().getlist, separator_re)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2003-2011 John Goerzen & contributors
|
# Copyright (C) 2003-2015 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
|
||||||
@ -14,29 +14,33 @@
|
|||||||
# 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
|
||||||
|
|
||||||
from offlineimap import mbnames, CustomConfig, OfflineImapError
|
|
||||||
from offlineimap import globals
|
|
||||||
from offlineimap.repository import Repository
|
|
||||||
from offlineimap.ui import getglobalui
|
|
||||||
from offlineimap.threadutil import InstanceLimitedThread
|
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
from threading import Event
|
from threading import Event
|
||||||
import os
|
import os
|
||||||
from sys import exc_info
|
from sys import exc_info
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
from offlineimap import mbnames, CustomConfig, OfflineImapError
|
||||||
|
from offlineimap import globals
|
||||||
|
from offlineimap.repository import Repository
|
||||||
|
from offlineimap.ui import getglobalui
|
||||||
|
from offlineimap.threadutil import InstanceLimitedThread
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import fcntl
|
import fcntl
|
||||||
except:
|
except:
|
||||||
pass # ok if this fails, we can do without
|
pass # ok if this fails, we can do without
|
||||||
|
|
||||||
|
# FIXME: spaghetti code alert!
|
||||||
def getaccountlist(customconfig):
|
def getaccountlist(customconfig):
|
||||||
return customconfig.getsectionlist('Account')
|
return customconfig.getsectionlist('Account')
|
||||||
|
|
||||||
|
# FIXME: spaghetti code alert!
|
||||||
def AccountListGenerator(customconfig):
|
def AccountListGenerator(customconfig):
|
||||||
return [Account(customconfig, accountname)
|
return [Account(customconfig, accountname)
|
||||||
for accountname in getaccountlist(customconfig)]
|
for accountname in getaccountlist(customconfig)]
|
||||||
|
|
||||||
|
# FIXME: spaghetti code alert!
|
||||||
def AccountHashGenerator(customconfig):
|
def AccountHashGenerator(customconfig):
|
||||||
retval = {}
|
retval = {}
|
||||||
for item in AccountListGenerator(customconfig):
|
for item in AccountListGenerator(customconfig):
|
||||||
|
@ -10,6 +10,7 @@ class OfflineImapError(Exception):
|
|||||||
* **REPO**: Abort repository sync, continue with next account
|
* **REPO**: Abort repository sync, continue with next account
|
||||||
* **CRITICAL**: Immediately exit offlineimap
|
* **CRITICAL**: Immediately exit offlineimap
|
||||||
"""
|
"""
|
||||||
|
|
||||||
MESSAGE, FOLDER_RETRY, FOLDER, REPO, CRITICAL = 0, 10, 15, 20, 30
|
MESSAGE, FOLDER_RETRY, FOLDER, REPO, CRITICAL = 0, 10, 15, 20, 30
|
||||||
|
|
||||||
def __init__(self, reason, severity, errcode=None):
|
def __init__(self, reason, severity, errcode=None):
|
||||||
@ -26,6 +27,7 @@ class OfflineImapError(Exception):
|
|||||||
value). So far, no errcodes have been defined yet.
|
value). So far, no errcodes have been defined yet.
|
||||||
|
|
||||||
:type severity: OfflineImapError.ERROR value"""
|
:type severity: OfflineImapError.ERROR value"""
|
||||||
|
|
||||||
self.errcode = errcode
|
self.errcode = errcode
|
||||||
self.severity = severity
|
self.severity = severity
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Base folder support
|
# Base folder support
|
||||||
# Copyright (C) 2002-2011 John Goerzen & contributors
|
# Copyright (C) 2002-2015 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
|
||||||
@ -23,7 +23,6 @@ import offlineimap.accounts
|
|||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
from sys import exc_info
|
from sys import exc_info
|
||||||
import traceback
|
|
||||||
|
|
||||||
|
|
||||||
class BaseFolder(object):
|
class BaseFolder(object):
|
||||||
@ -113,6 +112,7 @@ class BaseFolder(object):
|
|||||||
def quickchanged(self, statusfolder):
|
def quickchanged(self, statusfolder):
|
||||||
""" Runs quick check for folder changes and returns changed
|
""" Runs quick check for folder changes and returns changed
|
||||||
status: True -- changed, False -- not changed.
|
status: True -- changed, False -- not changed.
|
||||||
|
|
||||||
:param statusfolder: keeps track of the last known folder state.
|
:param statusfolder: keeps track of the last known folder state.
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
@ -129,11 +129,13 @@ class BaseFolder(object):
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
def getvisiblename(self):
|
def getvisiblename(self):
|
||||||
"""The nametrans-transposed name of the folder's name"""
|
"""The nametrans-transposed name of the folder's name."""
|
||||||
|
|
||||||
return self.visiblename
|
return self.visiblename
|
||||||
|
|
||||||
def getexplainedname(self):
|
def getexplainedname(self):
|
||||||
""" Name that shows both real and nametrans-mangled values"""
|
"""Name that shows both real and nametrans-mangled values."""
|
||||||
|
|
||||||
if self.name == self.visiblename:
|
if self.name == self.visiblename:
|
||||||
return self.name
|
return self.name
|
||||||
else:
|
else:
|
||||||
@ -603,6 +605,7 @@ class BaseFolder(object):
|
|||||||
:param new_uid: (optional) If given, the old UID will be changed
|
:param new_uid: (optional) If given, the old UID will be changed
|
||||||
to a new UID. This allows backends efficient renaming of
|
to a new UID. This allows backends efficient renaming of
|
||||||
messages if the UID has changed."""
|
messages if the UID has changed."""
|
||||||
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def deletemessage(self, uid):
|
def deletemessage(self, uid):
|
||||||
@ -610,6 +613,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 deletemessages(self, uidlist):
|
def deletemessages(self, uidlist):
|
||||||
@ -617,6 +621,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."""
|
||||||
|
|
||||||
for uid in uidlist:
|
for uid in uidlist:
|
||||||
self.deletemessage(uid)
|
self.deletemessage(uid)
|
||||||
|
|
||||||
@ -632,6 +637,7 @@ class BaseFolder(object):
|
|||||||
:param statusfolder: A LocalStatusFolder instance
|
:param statusfolder: A LocalStatusFolder instance
|
||||||
:param register: whether we should register a new thread."
|
:param register: whether we should register a new thread."
|
||||||
:returns: Nothing on success, or raises an Exception."""
|
:returns: Nothing on success, or raises an Exception."""
|
||||||
|
|
||||||
# Sometimes, it could be the case that if a sync takes awhile,
|
# Sometimes, it could be the case that if a sync takes awhile,
|
||||||
# a message might be deleted from the maildir before it can be
|
# a message might be deleted from the maildir before it can be
|
||||||
# synced to the status cache. This is only a problem with
|
# synced to the status cache. This is only a problem with
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Local status cache virtual folder
|
# Local status cache virtual folder
|
||||||
# Copyright (C) 2002 - 2011 John Goerzen & contributors
|
# Copyright (C) 2002-2015 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
|
||||||
@ -15,10 +15,10 @@
|
|||||||
# 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
|
||||||
|
|
||||||
from .Base import BaseFolder
|
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from .Base import BaseFolder
|
||||||
|
|
||||||
class LocalStatusFolder(BaseFolder):
|
class LocalStatusFolder(BaseFolder):
|
||||||
"""LocalStatus backend implemented as a plain text file"""
|
"""LocalStatus backend implemented as a plain text file"""
|
||||||
@ -33,9 +33,9 @@ class LocalStatusFolder(BaseFolder):
|
|||||||
self.filename = os.path.join(self.getroot(), self.getfolderbasename())
|
self.filename = os.path.join(self.getroot(), self.getfolderbasename())
|
||||||
self.messagelist = {}
|
self.messagelist = {}
|
||||||
self.savelock = threading.Lock()
|
self.savelock = threading.Lock()
|
||||||
self.doautosave = self.config.getdefaultboolean("general", "fsync",
|
# Should we perform fsyncs as often as possible?
|
||||||
False)
|
self.doautosave = self.config.getdefaultboolean(
|
||||||
"""Should we perform fsyncs as often as possible?"""
|
"general", "fsync", False)
|
||||||
|
|
||||||
# Interface from BaseFolder
|
# Interface from BaseFolder
|
||||||
def storesmessages(self):
|
def storesmessages(self):
|
||||||
@ -63,13 +63,12 @@ class LocalStatusFolder(BaseFolder):
|
|||||||
|
|
||||||
|
|
||||||
def readstatus_v1(self, fp):
|
def readstatus_v1(self, fp):
|
||||||
"""
|
"""Read status folder in format version 1.
|
||||||
Read status folder in format version 1.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- fp: I/O object that points to the opened database file.
|
- fp: I/O object that points to the opened database file.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for line in fp.xreadlines():
|
for line in fp.xreadlines():
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
try:
|
try:
|
||||||
@ -86,13 +85,12 @@ class LocalStatusFolder(BaseFolder):
|
|||||||
|
|
||||||
|
|
||||||
def readstatus(self, fp):
|
def readstatus(self, fp):
|
||||||
"""
|
"""Read status file in the current format.
|
||||||
Read status file in the current format.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- fp: I/O object that points to the opened database file.
|
- fp: I/O object that points to the opened database file.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for line in fp.xreadlines():
|
for line in fp.xreadlines():
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
try:
|
try:
|
||||||
@ -164,11 +162,13 @@ class LocalStatusFolder(BaseFolder):
|
|||||||
|
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""Save changed data to disk. For this backend it is the same as saveall"""
|
"""Save changed data to disk. For this backend it is the same as saveall."""
|
||||||
|
|
||||||
self.saveall()
|
self.saveall()
|
||||||
|
|
||||||
def saveall(self):
|
def saveall(self):
|
||||||
"""Saves the entire messagelist to disk"""
|
"""Saves the entire messagelist to disk."""
|
||||||
|
|
||||||
with self.savelock:
|
with self.savelock:
|
||||||
file = open(self.filename + ".tmp", "wt")
|
file = open(self.filename + ".tmp", "wt")
|
||||||
file.write((self.magicline % self.cur_version) + "\n")
|
file.write((self.magicline % self.cur_version) + "\n")
|
||||||
@ -198,6 +198,7 @@ class LocalStatusFolder(BaseFolder):
|
|||||||
See folder/Base for detail. Note that savemessage() does not
|
See folder/Base for detail. Note that savemessage() does not
|
||||||
check against dryrun settings, so you need to ensure that
|
check against dryrun settings, so you need to ensure that
|
||||||
savemessage is never called in a dryrun mode."""
|
savemessage is never called in a dryrun mode."""
|
||||||
|
|
||||||
if uid < 0:
|
if uid < 0:
|
||||||
# We cannot assign a uid.
|
# We cannot assign a uid.
|
||||||
return uid
|
return uid
|
||||||
@ -235,6 +236,7 @@ class LocalStatusFolder(BaseFolder):
|
|||||||
|
|
||||||
def savemessageslabelsbulk(self, labels):
|
def savemessageslabelsbulk(self, labels):
|
||||||
"""Saves labels from a dictionary in a single database operation."""
|
"""Saves labels from a dictionary in a single database operation."""
|
||||||
|
|
||||||
for uid, lb in labels.items():
|
for uid, lb in labels.items():
|
||||||
self.messagelist[uid]['labels'] = lb
|
self.messagelist[uid]['labels'] = lb
|
||||||
self.save()
|
self.save()
|
||||||
@ -254,6 +256,7 @@ class LocalStatusFolder(BaseFolder):
|
|||||||
|
|
||||||
def savemessagesmtimebulk(self, mtimes):
|
def savemessagesmtimebulk(self, mtimes):
|
||||||
"""Saves mtimes from the mtimes dictionary in a single database operation."""
|
"""Saves mtimes from the mtimes dictionary in a single database operation."""
|
||||||
|
|
||||||
for uid, mt in mtimes.items():
|
for uid, mt in mtimes.items():
|
||||||
self.messagelist[uid]['mtime'] = mt
|
self.messagelist[uid]['mtime'] = mt
|
||||||
self.save()
|
self.save()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Maildir folder support
|
# Maildir folder support
|
||||||
# Copyright (C) 2002 - 2011 John Goerzen & contributors
|
# Copyright (C) 2002-2015 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
|
||||||
@ -19,15 +19,12 @@ import socket
|
|||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import tempfile
|
|
||||||
from .Base import BaseFolder
|
from .Base import BaseFolder
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from md5 import md5
|
from md5 import md5
|
||||||
|
|
||||||
try: # python 2.6 has set() built in
|
try: # python 2.6 has set() built in
|
||||||
set
|
set
|
||||||
except NameError:
|
except NameError:
|
||||||
@ -131,6 +128,7 @@ class MaildirFolder(BaseFolder):
|
|||||||
|
|
||||||
:returns: (prefix, UID, FMD5, flags). UID is a numeric "long"
|
:returns: (prefix, UID, FMD5, flags). UID is a numeric "long"
|
||||||
type. flags is a set() of Maildir flags"""
|
type. flags is a set() of Maildir flags"""
|
||||||
|
|
||||||
prefix, uid, fmd5, flags = None, None, None, set()
|
prefix, uid, fmd5, flags = None, None, None, set()
|
||||||
prefixmatch = self.re_prefixmatch.match(filename)
|
prefixmatch = self.re_prefixmatch.match(filename)
|
||||||
if prefixmatch:
|
if prefixmatch:
|
||||||
@ -227,7 +225,8 @@ class MaildirFolder(BaseFolder):
|
|||||||
|
|
||||||
# Interface from BaseFolder
|
# Interface from BaseFolder
|
||||||
def getmessage(self, uid):
|
def getmessage(self, uid):
|
||||||
"""Return the content of the message"""
|
"""Return the content of the message."""
|
||||||
|
|
||||||
filename = self.messagelist[uid]['filename']
|
filename = self.messagelist[uid]['filename']
|
||||||
filepath = os.path.join(self.getfullname(), filename)
|
filepath = os.path.join(self.getfullname(), filename)
|
||||||
file = open(filepath, 'rt')
|
file = open(filepath, 'rt')
|
||||||
@ -249,6 +248,7 @@ class MaildirFolder(BaseFolder):
|
|||||||
:param uid: The UID`None`, or a set of maildir flags
|
:param uid: The UID`None`, or a set of maildir flags
|
||||||
:param flags: A set of maildir flags
|
:param flags: A set of maildir flags
|
||||||
:returns: String containing unique message filename"""
|
:returns: String containing unique message filename"""
|
||||||
|
|
||||||
timeval, timeseq = _gettimeseq()
|
timeval, timeseq = _gettimeseq()
|
||||||
return '%d_%d.%d.%s,U=%d,FMD5=%s%s2,%s' % \
|
return '%d_%d.%d.%s,U=%d,FMD5=%s%s2,%s' % \
|
||||||
(timeval, timeseq, os.getpid(), socket.gethostname(),
|
(timeval, timeseq, os.getpid(), socket.gethostname(),
|
||||||
@ -256,8 +256,7 @@ class MaildirFolder(BaseFolder):
|
|||||||
|
|
||||||
|
|
||||||
def save_to_tmp_file(self, filename, content):
|
def save_to_tmp_file(self, filename, content):
|
||||||
"""
|
"""Saves given content to the named temporary file in the
|
||||||
Saves given content to the named temporary file in the
|
|
||||||
'tmp' subdirectory of $CWD.
|
'tmp' subdirectory of $CWD.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@ -265,9 +264,7 @@ class MaildirFolder(BaseFolder):
|
|||||||
- content: data to be saved.
|
- content: data to be saved.
|
||||||
|
|
||||||
Returns: relative path to the temporary file
|
Returns: relative path to the temporary file
|
||||||
that was created.
|
that was created."""
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
tmpname = os.path.join('tmp', filename)
|
tmpname = os.path.join('tmp', filename)
|
||||||
# open file and write it out
|
# open file and write it out
|
||||||
@ -364,7 +361,7 @@ class MaildirFolder(BaseFolder):
|
|||||||
infomatch = self.re_flagmatch.search(filename)
|
infomatch = self.re_flagmatch.search(filename)
|
||||||
if infomatch:
|
if infomatch:
|
||||||
filename = filename[:-len(infomatch.group())] #strip off
|
filename = filename[:-len(infomatch.group())] #strip off
|
||||||
infostr = '%s2,%s' % (self.infosep, ''.join(sorted(flags)))
|
infostr = '%s2,%s'% (self.infosep, ''.join(sorted(flags)))
|
||||||
filename += infostr
|
filename += infostr
|
||||||
|
|
||||||
newfilename = os.path.join(dir_prefix, filename)
|
newfilename = os.path.join(dir_prefix, filename)
|
||||||
@ -386,8 +383,10 @@ class MaildirFolder(BaseFolder):
|
|||||||
|
|
||||||
This will not update the statusfolder UID, you need to do that yourself.
|
This will not update the statusfolder UID, you need to do that yourself.
|
||||||
:param new_uid: (optional) If given, the old UID will be changed
|
:param new_uid: (optional) If given, the old UID will be changed
|
||||||
to a new UID. The Maildir backend can implement this as an efficient
|
to a new UID. The Maildir backend can implement this as
|
||||||
rename."""
|
an efficient rename.
|
||||||
|
"""
|
||||||
|
|
||||||
if not uid in self.messagelist:
|
if not uid in self.messagelist:
|
||||||
raise OfflineImapError("Cannot change unknown Maildir UID %s" % uid)
|
raise OfflineImapError("Cannot change unknown Maildir UID %s" % uid)
|
||||||
if uid == new_uid: return
|
if uid == new_uid: return
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Base folder support
|
# Base folder support
|
||||||
# Copyright (C) 2002-2012 John Goerzen & contributors
|
# Copyright (C) 2002-2015 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
|
||||||
@ -59,8 +59,8 @@ class MappedIMAPFolder(IMAPFolder):
|
|||||||
try:
|
try:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise Exception("Corrupt line '%s' in UID mapping file '%s'" \
|
raise Exception("Corrupt line '%s' in UID mapping file '%s'"%
|
||||||
%(line, mapfilename))
|
(line, mapfilename))
|
||||||
(str1, str2) = line.split(':')
|
(str1, str2) = line.split(':')
|
||||||
loc = long(str1)
|
loc = long(str1)
|
||||||
rem = long(str2)
|
rem = long(str2)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# imaplib utilities
|
# imaplib utilities
|
||||||
# Copyright (C) 2002-2007 John Goerzen <jgoerzen@complete.org>
|
# Copyright (C) 2002-2015 John Goerzen & contributors
|
||||||
# 2012-2012 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
|
||||||
# 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
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
@ -33,11 +32,12 @@ class UsefulIMAPMixIn(object):
|
|||||||
return self.mailbox
|
return self.mailbox
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def select(self, mailbox='INBOX', readonly=False, force = False):
|
def select(self, mailbox='INBOX', readonly=False, force=False):
|
||||||
"""Selects a mailbox on the IMAP server
|
"""Selects a mailbox on the IMAP server
|
||||||
|
|
||||||
:returns: 'OK' on success, nothing if the folder was already
|
:returns: 'OK' on success, nothing if the folder was already
|
||||||
selected or raises an :exc:`OfflineImapError`"""
|
selected or raises an :exc:`OfflineImapError`."""
|
||||||
|
|
||||||
if self.__getselectedfolder() == mailbox and self.is_readonly == readonly \
|
if self.__getselectedfolder() == mailbox and self.is_readonly == readonly \
|
||||||
and not force:
|
and not force:
|
||||||
# No change; return.
|
# No change; return.
|
||||||
@ -67,6 +67,7 @@ class UsefulIMAPMixIn(object):
|
|||||||
def _mesg(self, s, tn=None, secs=None):
|
def _mesg(self, s, tn=None, secs=None):
|
||||||
new_mesg(self, s, tn, secs)
|
new_mesg(self, s, tn, secs)
|
||||||
|
|
||||||
|
|
||||||
class IMAP4_Tunnel(UsefulIMAPMixIn, IMAP4):
|
class IMAP4_Tunnel(UsefulIMAPMixIn, IMAP4):
|
||||||
"""IMAP4 client class over a tunnel
|
"""IMAP4 client class over a tunnel
|
||||||
|
|
||||||
@ -80,6 +81,7 @@ class IMAP4_Tunnel(UsefulIMAPMixIn, IMAP4):
|
|||||||
|
|
||||||
def open(self, host, port):
|
def open(self, host, port):
|
||||||
"""The tunnelcmd comes in on host!"""
|
"""The tunnelcmd comes in on host!"""
|
||||||
|
|
||||||
self.host = host
|
self.host = host
|
||||||
self.process = subprocess.Popen(host, shell=True, close_fds=True,
|
self.process = subprocess.Popen(host, shell=True, close_fds=True,
|
||||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
@ -90,7 +92,8 @@ class IMAP4_Tunnel(UsefulIMAPMixIn, IMAP4):
|
|||||||
self.set_nonblocking(self.read_fd)
|
self.set_nonblocking(self.read_fd)
|
||||||
|
|
||||||
def set_nonblocking(self, fd):
|
def set_nonblocking(self, fd):
|
||||||
"Mark fd as nonblocking"
|
"""Mark fd as nonblocking"""
|
||||||
|
|
||||||
# get the file's current flag settings
|
# get the file's current flag settings
|
||||||
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
|
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||||
# clear non-blocking mode from flags
|
# clear non-blocking mode from flags
|
||||||
@ -115,10 +118,8 @@ class IMAP4_Tunnel(UsefulIMAPMixIn, IMAP4):
|
|||||||
if self.compressor is not None:
|
if self.compressor is not None:
|
||||||
data = self.compressor.compress(data)
|
data = self.compressor.compress(data)
|
||||||
data += self.compressor.flush(zlib.Z_SYNC_FLUSH)
|
data += self.compressor.flush(zlib.Z_SYNC_FLUSH)
|
||||||
|
|
||||||
self.outfd.write(data)
|
self.outfd.write(data)
|
||||||
|
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
self.infd.close()
|
self.infd.close()
|
||||||
self.outfd.close()
|
self.outfd.close()
|
||||||
@ -135,7 +136,8 @@ def new_mesg(self, s, tn=None, secs=None):
|
|||||||
|
|
||||||
|
|
||||||
class WrappedIMAP4_SSL(UsefulIMAPMixIn, IMAP4_SSL):
|
class WrappedIMAP4_SSL(UsefulIMAPMixIn, IMAP4_SSL):
|
||||||
"""Improved version of imaplib.IMAP4_SSL overriding select()"""
|
"""Improved version of imaplib.IMAP4_SSL overriding select()."""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self._fingerprint = kwargs.get('fingerprint', None)
|
self._fingerprint = kwargs.get('fingerprint', None)
|
||||||
if type(self._fingerprint) != type([]):
|
if type(self._fingerprint) != type([]):
|
||||||
@ -146,32 +148,34 @@ class WrappedIMAP4_SSL(UsefulIMAPMixIn, IMAP4_SSL):
|
|||||||
|
|
||||||
def open(self, host=None, port=None):
|
def open(self, host=None, port=None):
|
||||||
if not self.ca_certs and not self._fingerprint:
|
if not self.ca_certs and not self._fingerprint:
|
||||||
raise OfflineImapError("No CA certificates " + \
|
raise OfflineImapError("No CA certificates "
|
||||||
"and no server fingerprints configured. " + \
|
"and no server fingerprints configured. "
|
||||||
"You must configure at least something, otherwise " + \
|
"You must configure at least something, otherwise "
|
||||||
"having SSL helps nothing.", OfflineImapError.ERROR.REPO)
|
"having SSL helps nothing.", OfflineImapError.ERROR.REPO)
|
||||||
super(WrappedIMAP4_SSL, self).open(host, port)
|
super(WrappedIMAP4_SSL, self).open(host, port)
|
||||||
if self._fingerprint:
|
if self._fingerprint:
|
||||||
# compare fingerprints
|
# compare fingerprints
|
||||||
fingerprint = sha1(self.sock.getpeercert(True)).hexdigest()
|
fingerprint = sha1(self.sock.getpeercert(True)).hexdigest()
|
||||||
if fingerprint not in self._fingerprint:
|
if fingerprint not in self._fingerprint:
|
||||||
raise OfflineImapError("Server SSL fingerprint '%s' " % fingerprint + \
|
raise OfflineImapError("Server SSL fingerprint '%s' "
|
||||||
"for hostname '%s' " % host + \
|
"for hostname '%s' "
|
||||||
"does not match configured fingerprint(s) %s. " % self._fingerprint + \
|
"does not match configured fingerprint(s) %s. "
|
||||||
"Please verify and set 'cert_fingerprint' accordingly " + \
|
"Please verify and set 'cert_fingerprint' accordingly "
|
||||||
"if not set yet.", OfflineImapError.ERROR.REPO)
|
"if not set yet."%
|
||||||
|
(fingerprint, host, self._fingerprint),
|
||||||
|
OfflineImapError.ERROR.REPO)
|
||||||
|
|
||||||
|
|
||||||
class WrappedIMAP4(UsefulIMAPMixIn, IMAP4):
|
class WrappedIMAP4(UsefulIMAPMixIn, IMAP4):
|
||||||
"""Improved version of imaplib.IMAP4 overriding select()"""
|
"""Improved version of imaplib.IMAP4 overriding select()."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def Internaldate2epoch(resp):
|
def Internaldate2epoch(resp):
|
||||||
"""Convert IMAP4 INTERNALDATE to UT.
|
"""Convert IMAP4 INTERNALDATE to UT.
|
||||||
|
|
||||||
Returns seconds since the epoch.
|
Returns seconds since the epoch."""
|
||||||
"""
|
|
||||||
|
|
||||||
mo = InternalDate.match(resp)
|
mo = InternalDate.match(resp)
|
||||||
if not mo:
|
if not mo:
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# IMAP utility module
|
# IMAP utility module
|
||||||
# Copyright (C) 2002 John Goerzen
|
# Copyright (C) 2002-2015 John Goerzen & contributors
|
||||||
# <jgoerzen@complete.org>
|
|
||||||
#
|
#
|
||||||
# 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
|
||||||
@ -37,8 +36,8 @@ def dequote(string):
|
|||||||
"""Takes string which may or may not be quoted and unquotes it.
|
"""Takes string which may or may not be quoted and unquotes it.
|
||||||
|
|
||||||
It only considers double quotes. This function does NOT consider
|
It only considers double quotes. This function does NOT consider
|
||||||
parenthised lists to be quoted.
|
parenthised lists to be quoted."""
|
||||||
"""
|
|
||||||
if string and string.startswith('"') and string.endswith('"'):
|
if string and string.startswith('"') and string.endswith('"'):
|
||||||
string = string[1:-1] # Strip off the surrounding quotes.
|
string = string[1:-1] # Strip off the surrounding quotes.
|
||||||
string = string.replace('\\"', '"')
|
string = string.replace('\\"', '"')
|
||||||
@ -49,8 +48,8 @@ def quote(string):
|
|||||||
"""Takes an unquoted string and quotes it.
|
"""Takes an unquoted string and quotes it.
|
||||||
|
|
||||||
It only adds double quotes. This function does NOT consider
|
It only adds double quotes. This function does NOT consider
|
||||||
parenthised lists to be quoted.
|
parenthised lists to be quoted."""
|
||||||
"""
|
|
||||||
string = string.replace('"', '\\"')
|
string = string.replace('"', '\\"')
|
||||||
string = string.replace('\\', '\\\\')
|
string = string.replace('\\', '\\\\')
|
||||||
return '"%s"' % string
|
return '"%s"' % string
|
||||||
@ -62,12 +61,14 @@ def flagsplit(string):
|
|||||||
(FLAGS (\\Seen Old) UID 4807) returns
|
(FLAGS (\\Seen Old) UID 4807) returns
|
||||||
['FLAGS,'(\\Seen Old)','UID', '4807']
|
['FLAGS,'(\\Seen Old)','UID', '4807']
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if string[0] != '(' or string[-1] != ')':
|
if string[0] != '(' or string[-1] != ')':
|
||||||
raise ValueError("Passed string '%s' is not a flag list" % string)
|
raise ValueError("Passed string '%s' is not a flag list" % string)
|
||||||
return imapsplit(string[1:-1])
|
return imapsplit(string[1:-1])
|
||||||
|
|
||||||
def __options2hash(list):
|
def __options2hash(list):
|
||||||
"""convert list [1,2,3,4,5,6] to {1:2, 3:4, 5:6}"""
|
"""convert list [1,2,3,4,5,6] to {1:2, 3:4, 5:6}"""
|
||||||
|
|
||||||
# effectively this does dict(zip(l[::2],l[1::2])), however
|
# effectively this does dict(zip(l[::2],l[1::2])), however
|
||||||
# measurements seemed to have indicated that the manual variant is
|
# measurements seemed to have indicated that the manual variant is
|
||||||
# faster for mosly small lists.
|
# faster for mosly small lists.
|
||||||
@ -84,6 +85,7 @@ def flags2hash(flags):
|
|||||||
|
|
||||||
E.g. '(FLAGS (\\Seen Old) UID 4807)' leads to
|
E.g. '(FLAGS (\\Seen Old) UID 4807)' leads to
|
||||||
{'FLAGS': '(\\Seen Old)', 'UID': '4807'}"""
|
{'FLAGS': '(\\Seen Old)', 'UID': '4807'}"""
|
||||||
|
|
||||||
return __options2hash(flagsplit(flags))
|
return __options2hash(flagsplit(flags))
|
||||||
|
|
||||||
def imapsplit(imapstring):
|
def imapsplit(imapstring):
|
||||||
@ -182,7 +184,8 @@ flagmap = [('\\Seen', 'S'),
|
|||||||
('\\Draft', 'D')]
|
('\\Draft', 'D')]
|
||||||
|
|
||||||
def flagsimap2maildir(flagstring):
|
def flagsimap2maildir(flagstring):
|
||||||
"""Convert string '(\\Draft \\Deleted)' into a flags set(DR)"""
|
"""Convert string '(\\Draft \\Deleted)' into a flags set(DR)."""
|
||||||
|
|
||||||
retval = set()
|
retval = set()
|
||||||
imapflaglist = flagstring[1:-1].split()
|
imapflaglist = flagstring[1:-1].split()
|
||||||
for imapflag, maildirflag in flagmap:
|
for imapflag, maildirflag in flagmap:
|
||||||
@ -191,7 +194,8 @@ def flagsimap2maildir(flagstring):
|
|||||||
return retval
|
return retval
|
||||||
|
|
||||||
def flagsmaildir2imap(maildirflaglist):
|
def flagsmaildir2imap(maildirflaglist):
|
||||||
"""Convert set of flags ([DR]) into a string '(\\Deleted \\Draft)'"""
|
"""Convert set of flags ([DR]) into a string '(\\Deleted \\Draft)'."""
|
||||||
|
|
||||||
retval = []
|
retval = []
|
||||||
for imapflag, maildirflag in flagmap:
|
for imapflag, maildirflag in flagmap:
|
||||||
if maildirflag in maildirflaglist:
|
if maildirflag in maildirflaglist:
|
||||||
@ -203,7 +207,8 @@ def uid_sequence(uidlist):
|
|||||||
|
|
||||||
[1,2,3,4,5,10,12,13] will return "1:5,10,12:13". This function sorts
|
[1,2,3,4,5,10,12,13] will return "1:5,10,12:13". This function sorts
|
||||||
the list, and only collapses if subsequent entries form a range.
|
the list, and only collapses if subsequent entries form a range.
|
||||||
:returns: The collapsed UID list as string"""
|
:returns: The collapsed UID list as string."""
|
||||||
|
|
||||||
def getrange(start, end):
|
def getrange(start, end):
|
||||||
if start == end:
|
if start == end:
|
||||||
return(str(start))
|
return(str(start))
|
||||||
@ -230,8 +235,7 @@ def uid_sequence(uidlist):
|
|||||||
|
|
||||||
|
|
||||||
def __split_quoted(string):
|
def __split_quoted(string):
|
||||||
"""
|
"""Looks for the ending quote character in the string that starts
|
||||||
Looks for the ending quote character in the string that starts
|
|
||||||
with quote character, splitting out quoted component and the
|
with quote character, splitting out quoted component and the
|
||||||
rest of the string (without possible space between these two
|
rest of the string (without possible space between these two
|
||||||
parts.
|
parts.
|
||||||
@ -241,7 +245,6 @@ def __split_quoted(string):
|
|||||||
Examples:
|
Examples:
|
||||||
- "this is \" a test" (\\None) => ("this is \" a test", (\\None))
|
- "this is \" a test" (\\None) => ("this is \" a test", (\\None))
|
||||||
- "\\" => ("\\", )
|
- "\\" => ("\\", )
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if len(string) == 0:
|
if len(string) == 0:
|
||||||
@ -269,17 +272,15 @@ def __split_quoted(string):
|
|||||||
|
|
||||||
|
|
||||||
def format_labels_string(header, labels):
|
def format_labels_string(header, labels):
|
||||||
"""
|
"""Formats labels for embedding into a message,
|
||||||
Formats labels for embedding into a message,
|
|
||||||
with format according to header name.
|
with format according to header name.
|
||||||
|
|
||||||
Headers from SPACE_SEPARATED_LABEL_HEADERS keep space-separated list
|
Headers from SPACE_SEPARATED_LABEL_HEADERS keep space-separated list
|
||||||
of labels, the rest uses comma (',') as the separator.
|
of labels, the rest uses comma (',') as the separator.
|
||||||
|
|
||||||
Also see parse_labels_string() and modify it accordingly
|
Also see parse_labels_string() and modify it accordingly
|
||||||
if logics here gets changed.
|
if logics here gets changed."""
|
||||||
|
|
||||||
"""
|
|
||||||
if header in SPACE_SEPARATED_LABEL_HEADERS:
|
if header in SPACE_SEPARATED_LABEL_HEADERS:
|
||||||
sep = ' '
|
sep = ' '
|
||||||
else:
|
else:
|
||||||
@ -289,18 +290,16 @@ def format_labels_string(header, labels):
|
|||||||
|
|
||||||
|
|
||||||
def parse_labels_string(header, labels_str):
|
def parse_labels_string(header, labels_str):
|
||||||
"""
|
"""Parses a string into a set of labels, with a format according to
|
||||||
Parses a string into a set of labels, with a format according to
|
|
||||||
the name of the header.
|
the name of the header.
|
||||||
|
|
||||||
See __format_labels_string() for explanation on header handling
|
See __format_labels_string() for explanation on header handling
|
||||||
and keep these two functions synced with each other.
|
and keep these two functions synced with each other.
|
||||||
|
|
||||||
TODO: add test to ensure that
|
TODO: add test to ensure that
|
||||||
format_labels_string * parse_labels_string is unity
|
- format_labels_string * parse_labels_string is unity
|
||||||
and
|
and
|
||||||
parse_labels_string * format_labels_string is unity
|
- parse_labels_string * format_labels_string is unity
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if header in SPACE_SEPARATED_LABEL_HEADERS:
|
if header in SPACE_SEPARATED_LABEL_HEADERS:
|
||||||
@ -314,15 +313,13 @@ def parse_labels_string(header, labels_str):
|
|||||||
|
|
||||||
|
|
||||||
def labels_from_header(header_name, header_value):
|
def labels_from_header(header_name, header_value):
|
||||||
"""
|
"""Helper that builds label set from the corresponding header value.
|
||||||
Helper that builds label set from the corresponding header value.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- header_name: name of the header that keeps labels;
|
- header_name: name of the header that keeps labels;
|
||||||
- header_value: value of the said header, can be None
|
- header_value: value of the said header, can be None
|
||||||
|
|
||||||
Returns: set of labels parsed from the header (or empty set).
|
Returns: set of labels parsed from the header (or empty set).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if header_value:
|
if header_value:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# OfflineIMAP initialization code
|
# OfflineIMAP initialization code
|
||||||
# Copyright (C) 2002-2011 John Goerzen & contributors
|
# Copyright (C) 2002-2015 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
|
||||||
@ -214,7 +214,7 @@ class OfflineImap:
|
|||||||
config.set(section, key, value)
|
config.set(section, key, value)
|
||||||
|
|
||||||
#which ui to use? cmd line option overrides config file
|
#which ui to use? cmd line option overrides config file
|
||||||
ui_type = config.getdefault('general','ui', 'ttyui')
|
ui_type = config.getdefault('general', 'ui', 'ttyui')
|
||||||
if options.interface != None:
|
if options.interface != None:
|
||||||
ui_type = options.interface
|
ui_type = options.interface
|
||||||
if '.' in ui_type:
|
if '.' in ui_type:
|
||||||
@ -222,13 +222,13 @@ class OfflineImap:
|
|||||||
ui_type = ui_type.split('.')[-1]
|
ui_type = ui_type.split('.')[-1]
|
||||||
# TODO, make use of chosen ui for logging
|
# TODO, make use of chosen ui for logging
|
||||||
logging.warning('Using old interface name, consider using one '
|
logging.warning('Using old interface name, consider using one '
|
||||||
'of %s' % ', '.join(UI_LIST.keys()))
|
'of %s'% ', '.join(UI_LIST.keys()))
|
||||||
if options.diagnostics: ui_type = 'basic' # enforce basic UI for --info
|
if options.diagnostics: ui_type = 'basic' # enforce basic UI for --info
|
||||||
|
|
||||||
#dry-run? Set [general]dry-run=True
|
#dry-run? Set [general]dry-run=True
|
||||||
if options.dryrun:
|
if options.dryrun:
|
||||||
dryrun = config.set('general','dry-run', "True")
|
dryrun = config.set('general', 'dry-run', 'True')
|
||||||
config.set_if_not_exists('general','dry-run','False')
|
config.set_if_not_exists('general', 'dry-run', 'False')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# create the ui class
|
# create the ui class
|
||||||
@ -264,7 +264,7 @@ class OfflineImap:
|
|||||||
imaplib.Debug = 5
|
imaplib.Debug = 5
|
||||||
|
|
||||||
if options.runonce:
|
if options.runonce:
|
||||||
# FIXME: maybe need a better
|
# FIXME: spaghetti code alert!
|
||||||
for section in accounts.getaccountlist(config):
|
for section in accounts.getaccountlist(config):
|
||||||
config.remove_option('Account ' + section, "autorefresh")
|
config.remove_option('Account ' + section, "autorefresh")
|
||||||
|
|
||||||
@ -275,7 +275,7 @@ class OfflineImap:
|
|||||||
#custom folder list specified?
|
#custom folder list specified?
|
||||||
if options.folders:
|
if options.folders:
|
||||||
foldernames = options.folders.split(",")
|
foldernames = options.folders.split(",")
|
||||||
folderfilter = "lambda f: f in %s" % foldernames
|
folderfilter = "lambda f: f in %s"% foldernames
|
||||||
folderincludes = "[]"
|
folderincludes = "[]"
|
||||||
for accountname in accounts.getaccountlist(config):
|
for accountname in accounts.getaccountlist(config):
|
||||||
account_section = 'Account ' + accountname
|
account_section = 'Account ' + accountname
|
||||||
@ -355,12 +355,12 @@ class OfflineImap:
|
|||||||
"take a few seconds)...")
|
"take a few seconds)...")
|
||||||
accounts.Account.set_abort_event(self.config, 3)
|
accounts.Account.set_abort_event(self.config, 3)
|
||||||
elif sig == signal.SIGQUIT:
|
elif sig == signal.SIGQUIT:
|
||||||
stacktrace.dump (sys.stderr)
|
stacktrace.dump(sys.stderr)
|
||||||
os.abort()
|
os.abort()
|
||||||
|
|
||||||
signal.signal(signal.SIGHUP,sig_handler)
|
signal.signal(signal.SIGHUP, sig_handler)
|
||||||
signal.signal(signal.SIGUSR1,sig_handler)
|
signal.signal(signal.SIGUSR1, sig_handler)
|
||||||
signal.signal(signal.SIGUSR2,sig_handler)
|
signal.signal(signal.SIGUSR2, sig_handler)
|
||||||
signal.signal(signal.SIGTERM, sig_handler)
|
signal.signal(signal.SIGTERM, sig_handler)
|
||||||
signal.signal(signal.SIGINT, sig_handler)
|
signal.signal(signal.SIGINT, sig_handler)
|
||||||
signal.signal(signal.SIGQUIT, sig_handler)
|
signal.signal(signal.SIGQUIT, sig_handler)
|
||||||
@ -394,7 +394,7 @@ class OfflineImap:
|
|||||||
for accountname in accs:
|
for accountname in accs:
|
||||||
account = offlineimap.accounts.SyncableAccount(self.config,
|
account = offlineimap.accounts.SyncableAccount(self.config,
|
||||||
accountname)
|
accountname)
|
||||||
threading.currentThread().name = "Account sync %s" % accountname
|
threading.currentThread().name = "Account sync %s"% accountname
|
||||||
account.syncrunner()
|
account.syncrunner()
|
||||||
|
|
||||||
def __serverdiagnostics(self, options):
|
def __serverdiagnostics(self, options):
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Mailbox name generator
|
# Mailbox name generator
|
||||||
# Copyright (C) 2002 John Goerzen
|
#
|
||||||
# <jgoerzen@complete.org>
|
# Copyright (C) 2002-2015 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
|
||||||
@ -49,6 +49,7 @@ def write():
|
|||||||
def __genmbnames():
|
def __genmbnames():
|
||||||
"""Takes a configparser object and a boxlist, which is a list of hashes
|
"""Takes a configparser object and a boxlist, which is a list of hashes
|
||||||
containing 'accountname' and 'foldername' keys."""
|
containing 'accountname' and 'foldername' keys."""
|
||||||
|
|
||||||
xforms = [os.path.expanduser, os.path.expandvars]
|
xforms = [os.path.expanduser, os.path.expandvars]
|
||||||
mblock.acquire()
|
mblock.acquire()
|
||||||
try:
|
try:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Base repository support
|
# Base repository support
|
||||||
# Copyright (C) 2002-2012 John Goerzen & contributors
|
# Copyright (C) 2002-2015 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
|
||||||
@ -18,6 +18,7 @@
|
|||||||
import re
|
import re
|
||||||
import os.path
|
import os.path
|
||||||
from sys import exc_info
|
from sys import exc_info
|
||||||
|
|
||||||
from offlineimap import CustomConfig
|
from offlineimap import CustomConfig
|
||||||
from offlineimap.ui import getglobalui
|
from offlineimap.ui import getglobalui
|
||||||
from offlineimap.error import OfflineImapError
|
from offlineimap.error import OfflineImapError
|
||||||
@ -113,6 +114,7 @@ class BaseRepository(CustomConfig.ConfigHelperMixin, object):
|
|||||||
@property
|
@property
|
||||||
def readonly(self):
|
def readonly(self):
|
||||||
"""Is the repository readonly?"""
|
"""Is the repository readonly?"""
|
||||||
|
|
||||||
return self._readonly
|
return self._readonly
|
||||||
|
|
||||||
def getlocaleval(self):
|
def getlocaleval(self):
|
||||||
@ -120,11 +122,13 @@ class BaseRepository(CustomConfig.ConfigHelperMixin, object):
|
|||||||
|
|
||||||
def getfolders(self):
|
def getfolders(self):
|
||||||
"""Returns a list of ALL folders on this server."""
|
"""Returns a list of ALL folders on this server."""
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def forgetfolders(self):
|
def forgetfolders(self):
|
||||||
"""Forgets the cached list of folders, if any. Useful to run
|
"""Forgets the cached list of folders, if any. Useful to run
|
||||||
after a sync run."""
|
after a sync run."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def getsep(self):
|
def getsep(self):
|
||||||
@ -132,6 +136,7 @@ class BaseRepository(CustomConfig.ConfigHelperMixin, object):
|
|||||||
|
|
||||||
def should_sync_folder(self, fname):
|
def should_sync_folder(self, fname):
|
||||||
"""Should this folder be synced?"""
|
"""Should this folder be synced?"""
|
||||||
|
|
||||||
return fname in self.folderincludes or self.folderfilter(fname)
|
return fname in self.folderincludes or self.folderfilter(fname)
|
||||||
|
|
||||||
def get_create_folders(self):
|
def get_create_folders(self):
|
||||||
@ -139,11 +144,13 @@ class BaseRepository(CustomConfig.ConfigHelperMixin, object):
|
|||||||
|
|
||||||
It is disabled by either setting the whole repository
|
It is disabled by either setting the whole repository
|
||||||
'readonly' or by using the 'createfolders' setting."""
|
'readonly' or by using the 'createfolders' setting."""
|
||||||
|
|
||||||
return (not self._readonly) and \
|
return (not self._readonly) and \
|
||||||
self.getconfboolean('createfolders', True)
|
self.getconfboolean('createfolders', True)
|
||||||
|
|
||||||
def makefolder(self, foldername):
|
def makefolder(self, foldername):
|
||||||
"""Create a new folder"""
|
"""Create a new folder"""
|
||||||
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def deletefolder(self, foldername):
|
def deletefolder(self, foldername):
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# IMAP repository support
|
# IMAP repository support
|
||||||
# Copyright (C) 2002-2011 John Goerzen & contributors
|
# Copyright (C) 2002-2015 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
|
||||||
@ -15,17 +15,19 @@
|
|||||||
# 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
|
||||||
|
|
||||||
from offlineimap.repository.Base import BaseRepository
|
|
||||||
from offlineimap import folder, imaputil, imapserver, OfflineImapError
|
|
||||||
from offlineimap.folder.UIDMaps import MappedIMAPFolder
|
|
||||||
from offlineimap.threadutil import ExitNotifyThread
|
|
||||||
from offlineimap.utils.distro import get_os_sslcertfile
|
|
||||||
from threading import Event
|
from threading import Event
|
||||||
import os
|
import os
|
||||||
from sys import exc_info
|
from sys import exc_info
|
||||||
import netrc
|
import netrc
|
||||||
import errno
|
import errno
|
||||||
|
|
||||||
|
from offlineimap.repository.Base import BaseRepository
|
||||||
|
from offlineimap import folder, imaputil, imapserver, OfflineImapError
|
||||||
|
from offlineimap.folder.UIDMaps import MappedIMAPFolder
|
||||||
|
from offlineimap.threadutil import ExitNotifyThread
|
||||||
|
from offlineimap.utils.distro import get_os_sslcertfile
|
||||||
|
|
||||||
|
|
||||||
class IMAPRepository(BaseRepository):
|
class IMAPRepository(BaseRepository):
|
||||||
def __init__(self, reposname, account):
|
def __init__(self, reposname, account):
|
||||||
"""Initialize an IMAPRepository object."""
|
"""Initialize an IMAPRepository object."""
|
||||||
@ -116,14 +118,10 @@ class IMAPRepository(BaseRepository):
|
|||||||
"'%s' specified." % self,
|
"'%s' specified." % self,
|
||||||
OfflineImapError.ERROR.REPO)
|
OfflineImapError.ERROR.REPO)
|
||||||
|
|
||||||
|
|
||||||
def get_remote_identity(self):
|
def get_remote_identity(self):
|
||||||
"""
|
"""Remote identity is used for certain SASL mechanisms
|
||||||
Remote identity is used for certain SASL mechanisms
|
|
||||||
(currently -- PLAIN) to inform server about the ID
|
(currently -- PLAIN) to inform server about the ID
|
||||||
we want to authorize as instead of our login name.
|
we want to authorize as instead of our login name."""
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.getconf('remote_identity', default=None)
|
return self.getconf('remote_identity', default=None)
|
||||||
|
|
||||||
@ -218,13 +216,10 @@ class IMAPRepository(BaseRepository):
|
|||||||
return self.getconf('ssl_version', None)
|
return self.getconf('ssl_version', None)
|
||||||
|
|
||||||
def get_ssl_fingerprint(self):
|
def get_ssl_fingerprint(self):
|
||||||
"""
|
"""Return array of possible certificate fingerprints.
|
||||||
Return array of possible certificate fingerprints.
|
|
||||||
|
|
||||||
Configuration item cert_fingerprint can contain multiple
|
Configuration item cert_fingerprint can contain multiple
|
||||||
comma-separated fingerprints in hex form.
|
comma-separated fingerprints in hex form."""
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
value = self.getconf('cert_fingerprint', "")
|
value = self.getconf('cert_fingerprint', "")
|
||||||
return [f.strip().lower() for f in value.split(',') if f]
|
return [f.strip().lower() for f in value.split(',') if f]
|
||||||
@ -262,8 +257,8 @@ class IMAPRepository(BaseRepository):
|
|||||||
5. read password from /etc/netrc
|
5. read password from /etc/netrc
|
||||||
|
|
||||||
On success we return the password.
|
On success we return the password.
|
||||||
If all strategies fail we return None.
|
If all strategies fail we return None."""
|
||||||
"""
|
|
||||||
# 1. evaluate Repository 'remotepasseval'
|
# 1. evaluate Repository 'remotepasseval'
|
||||||
passwd = self.getconf('remotepasseval', None)
|
passwd = self.getconf('remotepasseval', None)
|
||||||
if passwd != None:
|
if passwd != None:
|
||||||
@ -304,7 +299,6 @@ class IMAPRepository(BaseRepository):
|
|||||||
# no strategy yielded a password!
|
# no strategy yielded a password!
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def getfolder(self, foldername):
|
def getfolder(self, foldername):
|
||||||
return self.getfoldertype()(self.imapserver, foldername, self)
|
return self.getfoldertype()(self.imapserver, foldername, self)
|
||||||
|
|
||||||
@ -392,6 +386,7 @@ class IMAPRepository(BaseRepository):
|
|||||||
when you are done creating folders yourself.
|
when you are done creating folders yourself.
|
||||||
|
|
||||||
:param foldername: Full path of the folder to be created."""
|
:param foldername: Full path of the folder to be created."""
|
||||||
|
|
||||||
if self.getreference():
|
if self.getreference():
|
||||||
foldername = self.getreference() + self.getsep() + foldername
|
foldername = self.getreference() + self.getsep() + foldername
|
||||||
if not foldername: # Create top level folder as folder separator
|
if not foldername: # Create top level folder as folder separator
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# Local status cache repository support
|
# Local status cache repository support
|
||||||
# Copyright (C) 2002 John Goerzen
|
# Copyright (C) 2002-2015 John Goerzen & contributors
|
||||||
# <jgoerzen@complete.org>
|
|
||||||
#
|
#
|
||||||
# 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
|
||||||
@ -81,7 +80,7 @@ class LocalStatusRepository(BaseRepository):
|
|||||||
return '.'
|
return '.'
|
||||||
|
|
||||||
def makefolder(self, foldername):
|
def makefolder(self, foldername):
|
||||||
"""Create a LocalStatus Folder"""
|
"""Create a LocalStatus Folder."""
|
||||||
|
|
||||||
if self.account.dryrun:
|
if self.account.dryrun:
|
||||||
return # bail out in dry-run mode
|
return # bail out in dry-run mode
|
||||||
@ -114,9 +113,11 @@ class LocalStatusRepository(BaseRepository):
|
|||||||
(see getfolderfilename) so we can not derive folder names from
|
(see getfolderfilename) so we can not derive folder names from
|
||||||
the file names that we have available. TODO: need to store a
|
the file names that we have available. TODO: need to store a
|
||||||
list of folder names somehow?"""
|
list of folder names somehow?"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def forgetfolders(self):
|
def forgetfolders(self):
|
||||||
"""Forgets the cached list of folders, if any. Useful to run
|
"""Forgets the cached list of folders, if any. Useful to run
|
||||||
after a sync run."""
|
after a sync run."""
|
||||||
|
|
||||||
self._folders = {}
|
self._folders = {}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# Maildir repository support
|
# Maildir repository support
|
||||||
# Copyright (C) 2002 John Goerzen
|
# Copyright (C) 2002-2015 John Goerzen & contributors
|
||||||
# <jgoerzen@complete.org>
|
|
||||||
#
|
#
|
||||||
# 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,6 +26,7 @@ class MaildirRepository(BaseRepository):
|
|||||||
def __init__(self, reposname, account):
|
def __init__(self, reposname, account):
|
||||||
"""Initialize a MaildirRepository object. Takes a path name
|
"""Initialize a MaildirRepository object. Takes a path name
|
||||||
to the directory holding all the Maildir directories."""
|
to the directory holding all the Maildir directories."""
|
||||||
|
|
||||||
BaseRepository.__init__(self, reposname, account)
|
BaseRepository.__init__(self, reposname, account)
|
||||||
|
|
||||||
self.root = self.getlocalroot()
|
self.root = self.getlocalroot()
|
||||||
@ -41,6 +41,7 @@ class MaildirRepository(BaseRepository):
|
|||||||
|
|
||||||
def _append_folder_atimes(self, foldername):
|
def _append_folder_atimes(self, foldername):
|
||||||
"""Store the atimes of a folder's new|cur in self.folder_atimes"""
|
"""Store the atimes of a folder's new|cur in self.folder_atimes"""
|
||||||
|
|
||||||
p = os.path.join(self.root, foldername)
|
p = os.path.join(self.root, foldername)
|
||||||
new = os.path.join(p, 'new')
|
new = os.path.join(p, 'new')
|
||||||
cur = os.path.join(p, 'cur')
|
cur = os.path.join(p, 'cur')
|
||||||
@ -51,6 +52,7 @@ class MaildirRepository(BaseRepository):
|
|||||||
"""Sets folders' atime back to their values after a sync
|
"""Sets folders' atime back to their values after a sync
|
||||||
|
|
||||||
Controlled by the 'restoreatime' config parameter."""
|
Controlled by the 'restoreatime' config parameter."""
|
||||||
|
|
||||||
if not self.getconfboolean('restoreatime', False):
|
if not self.getconfboolean('restoreatime', False):
|
||||||
return # not configured to restore
|
return # not configured to restore
|
||||||
|
|
||||||
@ -82,6 +84,7 @@ class MaildirRepository(BaseRepository):
|
|||||||
levels will be created if they do not exist yet. 'cur',
|
levels will be created if they do not exist yet. 'cur',
|
||||||
'tmp', and 'new' subfolders will be created in the maildir.
|
'tmp', and 'new' subfolders will be created in the maildir.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.ui.makefolder(self, foldername)
|
self.ui.makefolder(self, foldername)
|
||||||
if self.account.dryrun:
|
if self.account.dryrun:
|
||||||
return
|
return
|
||||||
@ -134,7 +137,7 @@ class MaildirRepository(BaseRepository):
|
|||||||
"folder '%s'." % foldername,
|
"folder '%s'." % foldername,
|
||||||
OfflineImapError.ERROR.FOLDER)
|
OfflineImapError.ERROR.FOLDER)
|
||||||
|
|
||||||
def _getfolders_scandir(self, root, extension = None):
|
def _getfolders_scandir(self, root, extension=None):
|
||||||
"""Recursively scan folder 'root'; return a list of MailDirFolder
|
"""Recursively scan folder 'root'; return a list of MailDirFolder
|
||||||
|
|
||||||
:param root: (absolute) path to Maildir root
|
:param root: (absolute) path to Maildir root
|
||||||
@ -200,4 +203,5 @@ class MaildirRepository(BaseRepository):
|
|||||||
def forgetfolders(self):
|
def forgetfolders(self):
|
||||||
"""Forgets the cached list of folders, if any. Useful to run
|
"""Forgets the cached list of folders, if any. Useful to run
|
||||||
after a sync run."""
|
after a sync run."""
|
||||||
|
|
||||||
self.folders = None
|
self.folders = None
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Curses-based interfaces
|
# Curses-based interfaces
|
||||||
# Copyright (C) 2003-2011 John Goerzen & contributors
|
# Copyright (C) 2003-2015 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
|
||||||
@ -22,12 +22,13 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import curses
|
import curses
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from offlineimap.ui.UIBase import UIBase
|
from offlineimap.ui.UIBase import UIBase
|
||||||
from offlineimap.threadutil import ExitNotifyThread
|
from offlineimap.threadutil import ExitNotifyThread
|
||||||
import offlineimap
|
import offlineimap
|
||||||
|
|
||||||
class CursesUtil:
|
|
||||||
|
|
||||||
|
class CursesUtil:
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# iolock protects access to the
|
# iolock protects access to the
|
||||||
self.iolock = RLock()
|
self.iolock = RLock()
|
||||||
@ -322,6 +323,7 @@ class Blinkenlights(UIBase, CursesUtil):
|
|||||||
|
|
||||||
Sets up things and adds them to self.logger.
|
Sets up things and adds them to self.logger.
|
||||||
:returns: The logging.Handler() for console output"""
|
:returns: The logging.Handler() for console output"""
|
||||||
|
|
||||||
# create console handler with a higher log level
|
# create console handler with a higher log level
|
||||||
ch = CursesLogHandler()
|
ch = CursesLogHandler()
|
||||||
#ch.setLevel(logging.DEBUG)
|
#ch.setLevel(logging.DEBUG)
|
||||||
@ -336,6 +338,7 @@ class Blinkenlights(UIBase, CursesUtil):
|
|||||||
|
|
||||||
def isusable(s):
|
def isusable(s):
|
||||||
"""Returns true if the backend is usable ie Curses works"""
|
"""Returns true if the backend is usable ie Curses works"""
|
||||||
|
|
||||||
# Not a terminal? Can't use curses.
|
# Not a terminal? Can't use curses.
|
||||||
if not sys.stdout.isatty() and sys.stdin.isatty():
|
if not sys.stdout.isatty() and sys.stdin.isatty():
|
||||||
return False
|
return False
|
||||||
@ -391,6 +394,7 @@ class Blinkenlights(UIBase, CursesUtil):
|
|||||||
|
|
||||||
def acct(self, *args):
|
def acct(self, *args):
|
||||||
"""Output that we start syncing an account (and start counting)"""
|
"""Output that we start syncing an account (and start counting)"""
|
||||||
|
|
||||||
self.gettf().setcolor('purple')
|
self.gettf().setcolor('purple')
|
||||||
super(Blinkenlights, self).acct(*args)
|
super(Blinkenlights, self).acct(*args)
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2007-2011 John Goerzen & contributors
|
# Copyright (C) 2007-2015 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
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# TTY UI
|
# TTY UI
|
||||||
# Copyright (C) 2002-2011 John Goerzen & contributors
|
# Copyright (C) 2002-2015 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
|
||||||
@ -24,6 +24,7 @@ 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
|
||||||
logging.Formatter.__init__(self, *args, **kwargs)
|
logging.Formatter.__init__(self, *args, **kwargs)
|
||||||
@ -46,12 +47,14 @@ class TTYFormatter(logging.Formatter):
|
|||||||
log_str = " %s" % log_str
|
log_str = " %s" % log_str
|
||||||
return log_str
|
return log_str
|
||||||
|
|
||||||
|
|
||||||
class TTYUI(UIBase):
|
class TTYUI(UIBase):
|
||||||
def setup_consolehandler(self):
|
def setup_consolehandler(self):
|
||||||
"""Backend specific console handler
|
"""Backend specific console handler
|
||||||
|
|
||||||
Sets up things and adds them to self.logger.
|
Sets up things and adds them to self.logger.
|
||||||
:returns: The logging.Handler() for console output"""
|
:returns: The logging.Handler() for console output"""
|
||||||
|
|
||||||
# create console handler with a higher log level
|
# create console handler with a higher log level
|
||||||
ch = logging.StreamHandler()
|
ch = logging.StreamHandler()
|
||||||
#ch.setLevel(logging.DEBUG)
|
#ch.setLevel(logging.DEBUG)
|
||||||
@ -67,10 +70,12 @@ class TTYUI(UIBase):
|
|||||||
|
|
||||||
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
|
||||||
@ -97,6 +102,7 @@ class TTYUI(UIBase):
|
|||||||
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" % (
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# UI base class
|
# UI base class
|
||||||
# Copyright (C) 2002-2011 John Goerzen & contributors
|
# Copyright (C) 2002-2015 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
|
||||||
@ -45,22 +45,22 @@ def getglobalui():
|
|||||||
return globalui
|
return globalui
|
||||||
|
|
||||||
class UIBase(object):
|
class UIBase(object):
|
||||||
def __init__(self, config, loglevel = logging.INFO):
|
def __init__(self, config, loglevel=logging.INFO):
|
||||||
self.config = config
|
self.config = config
|
||||||
# Is this a 'dryrun'?
|
# Is this a 'dryrun'?
|
||||||
self.dryrun = config.getdefaultboolean('general', 'dry-run', False)
|
self.dryrun = config.getdefaultboolean('general', 'dry-run', False)
|
||||||
self.debuglist = []
|
self.debuglist = []
|
||||||
"""list of debugtypes we are supposed to log"""
|
# list of debugtypes we are supposed to log
|
||||||
self.debugmessages = {}
|
self.debugmessages = {}
|
||||||
"""debugmessages in a deque(v) per thread(k)"""
|
# debugmessages in a deque(v) per thread(k)
|
||||||
self.debugmsglen = 15
|
self.debugmsglen = 15
|
||||||
self.threadaccounts = {}
|
self.threadaccounts = {}
|
||||||
"""dict linking active threads (k) to account names (v)"""
|
# dict linking active threads (k) to account names (v)
|
||||||
self.acct_startimes = {}
|
self.acct_startimes = {}
|
||||||
"""linking active accounts with the time.time() when sync started"""
|
# linking active accounts with the time.time() when sync started
|
||||||
self.logfile = None
|
self.logfile = None
|
||||||
self.exc_queue = Queue()
|
self.exc_queue = Queue()
|
||||||
"""saves all occuring exceptions, so we can output them at the end"""
|
# saves all occuring exceptions, so we can output them at the end
|
||||||
# create logger with 'OfflineImap' app
|
# create logger with 'OfflineImap' app
|
||||||
self.logger = logging.getLogger('OfflineImap')
|
self.logger = logging.getLogger('OfflineImap')
|
||||||
self.logger.setLevel(loglevel)
|
self.logger.setLevel(loglevel)
|
||||||
@ -73,6 +73,7 @@ class UIBase(object):
|
|||||||
|
|
||||||
Sets up things and adds them to self.logger.
|
Sets up things and adds them to self.logger.
|
||||||
:returns: The logging.Handler() for console output"""
|
:returns: The logging.Handler() for console output"""
|
||||||
|
|
||||||
# create console handler with a higher log level
|
# create console handler with a higher log level
|
||||||
ch = logging.StreamHandler(sys.stdout)
|
ch = logging.StreamHandler(sys.stdout)
|
||||||
#ch.setLevel(logging.DEBUG)
|
#ch.setLevel(logging.DEBUG)
|
||||||
@ -94,12 +95,13 @@ class UIBase(object):
|
|||||||
# write out more verbose initial info blurb on the log file
|
# write out more verbose initial info blurb on the log file
|
||||||
p_ver = ".".join([str(x) for x in sys.version_info[0:3]])
|
p_ver = ".".join([str(x) for x in sys.version_info[0:3]])
|
||||||
msg = "OfflineImap %s starting...\n Python: %s Platform: %s\n "\
|
msg = "OfflineImap %s starting...\n Python: %s Platform: %s\n "\
|
||||||
"Args: %s" % (offlineimap.__bigversion__, p_ver, sys.platform,
|
"Args: %s"% (offlineimap.__bigversion__, p_ver, sys.platform,
|
||||||
" ".join(sys.argv))
|
" ".join(sys.argv))
|
||||||
self.logger.info(msg)
|
self.logger.info(msg)
|
||||||
|
|
||||||
def _msg(self, msg):
|
def _msg(self, msg):
|
||||||
"""Display a message."""
|
"""Display a message."""
|
||||||
|
|
||||||
# TODO: legacy function, rip out.
|
# TODO: legacy function, rip out.
|
||||||
self.info(msg)
|
self.info(msg)
|
||||||
|
|
||||||
@ -149,7 +151,8 @@ class UIBase(object):
|
|||||||
self._msg(traceback.format_tb(instant_traceback))
|
self._msg(traceback.format_tb(instant_traceback))
|
||||||
|
|
||||||
def registerthread(self, account):
|
def registerthread(self, account):
|
||||||
"""Register current thread as being associated with an account name"""
|
"""Register current thread as being associated with an account name."""
|
||||||
|
|
||||||
cur_thread = threading.currentThread()
|
cur_thread = threading.currentThread()
|
||||||
if cur_thread in self.threadaccounts:
|
if cur_thread in self.threadaccounts:
|
||||||
# was already associated with an old account, update info
|
# was already associated with an old account, update info
|
||||||
@ -162,15 +165,17 @@ class UIBase(object):
|
|||||||
self.threadaccounts[cur_thread] = account
|
self.threadaccounts[cur_thread] = account
|
||||||
|
|
||||||
def unregisterthread(self, thr):
|
def unregisterthread(self, thr):
|
||||||
"""Unregister a thread as being associated with an account name"""
|
"""Unregister a thread as being associated with an account name."""
|
||||||
|
|
||||||
if thr in self.threadaccounts:
|
if thr in self.threadaccounts:
|
||||||
del self.threadaccounts[thr]
|
del self.threadaccounts[thr]
|
||||||
self.debug('thread', "Unregister thread '%s'" % thr.getName())
|
self.debug('thread', "Unregister thread '%s'" % thr.getName())
|
||||||
|
|
||||||
def getthreadaccount(self, thr = None):
|
def getthreadaccount(self, thr=None):
|
||||||
"""Get Account() for a thread (current if None)
|
"""Get Account() for a thread (current if None)
|
||||||
|
|
||||||
If no account has been registered with this thread, return 'None'"""
|
If no account has been registered with this thread, return 'None'."""
|
||||||
|
|
||||||
if thr == None:
|
if thr == None:
|
||||||
thr = threading.currentThread()
|
thr = threading.currentThread()
|
||||||
if thr in self.threadaccounts:
|
if thr in self.threadaccounts:
|
||||||
@ -214,6 +219,7 @@ class UIBase(object):
|
|||||||
"""Return the type of a repository or Folder as string
|
"""Return the type of a repository or Folder as string
|
||||||
|
|
||||||
(IMAP, Gmail, Maildir, etc...)"""
|
(IMAP, Gmail, Maildir, etc...)"""
|
||||||
|
|
||||||
prelimname = object.__class__.__name__.split('.')[-1]
|
prelimname = object.__class__.__name__.split('.')[-1]
|
||||||
# Strip off extra stuff.
|
# Strip off extra stuff.
|
||||||
return re.sub('(Folder|Repository)', '', prelimname)
|
return re.sub('(Folder|Repository)', '', prelimname)
|
||||||
@ -222,6 +228,7 @@ class UIBase(object):
|
|||||||
"""Returns true if this UI object is usable in the current
|
"""Returns true if this UI object is usable in the current
|
||||||
environment. For instance, an X GUI would return true if it's
|
environment. For instance, an X GUI would return true if it's
|
||||||
being run in X with a valid DISPLAY setting, and false otherwise."""
|
being run in X with a valid DISPLAY setting, and false otherwise."""
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
################################################## INPUT
|
################################################## INPUT
|
||||||
@ -281,7 +288,8 @@ class UIBase(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def connecting(self, hostname, port):
|
def connecting(self, hostname, port):
|
||||||
"""Log 'Establishing connection to'"""
|
"""Log 'Establishing connection to'."""
|
||||||
|
|
||||||
if not self.logger.isEnabledFor(logging.INFO): return
|
if not self.logger.isEnabledFor(logging.INFO): return
|
||||||
displaystr = ''
|
displaystr = ''
|
||||||
hostname = hostname if hostname else ''
|
hostname = hostname if hostname else ''
|
||||||
@ -291,19 +299,22 @@ class UIBase(object):
|
|||||||
self.logger.info("Establishing connection%s" % displaystr)
|
self.logger.info("Establishing connection%s" % displaystr)
|
||||||
|
|
||||||
def acct(self, account):
|
def acct(self, account):
|
||||||
"""Output that we start syncing an account (and start counting)"""
|
"""Output that we start syncing an account (and start counting)."""
|
||||||
|
|
||||||
self.acct_startimes[account] = time.time()
|
self.acct_startimes[account] = time.time()
|
||||||
self.logger.info("*** Processing account %s" % account)
|
self.logger.info("*** Processing account %s" % account)
|
||||||
|
|
||||||
def acctdone(self, account):
|
def acctdone(self, account):
|
||||||
"""Output that we finished syncing an account (in which time)"""
|
"""Output that we finished syncing an account (in which time)."""
|
||||||
|
|
||||||
sec = time.time() - self.acct_startimes[account]
|
sec = time.time() - self.acct_startimes[account]
|
||||||
del self.acct_startimes[account]
|
del self.acct_startimes[account]
|
||||||
self.logger.info("*** Finished account '%s' in %d:%02d" %
|
self.logger.info("*** Finished account '%s' in %d:%02d" %
|
||||||
(account, sec // 60, sec % 60))
|
(account, sec // 60, sec % 60))
|
||||||
|
|
||||||
def syncfolders(self, src_repo, dst_repo):
|
def syncfolders(self, src_repo, dst_repo):
|
||||||
"""Log 'Copying folder structure...'"""
|
"""Log 'Copying folder structure...'."""
|
||||||
|
|
||||||
if self.logger.isEnabledFor(logging.DEBUG):
|
if self.logger.isEnabledFor(logging.DEBUG):
|
||||||
self.debug('', "Copying folder structure from %s to %s" %\
|
self.debug('', "Copying folder structure from %s to %s" %\
|
||||||
(src_repo, dst_repo))
|
(src_repo, dst_repo))
|
||||||
@ -328,12 +339,12 @@ class UIBase(object):
|
|||||||
def validityproblem(self, folder):
|
def validityproblem(self, folder):
|
||||||
self.logger.warning("UID validity problem for folder %s (repo %s) "
|
self.logger.warning("UID validity problem for folder %s (repo %s) "
|
||||||
"(saved %d; got %d); skipping it. Please see FAQ "
|
"(saved %d; got %d); skipping it. Please see FAQ "
|
||||||
"and manual on how to handle this." % \
|
"and manual on how to handle this."% \
|
||||||
(folder, folder.getrepository(),
|
(folder, folder.getrepository(),
|
||||||
folder.get_saveduidvalidity(), folder.get_uidvalidity()))
|
folder.get_saveduidvalidity(), folder.get_uidvalidity()))
|
||||||
|
|
||||||
def loadmessagelist(self, repos, folder):
|
def loadmessagelist(self, repos, folder):
|
||||||
self.logger.debug("Loading message list for %s[%s]" % (
|
self.logger.debug(u"Loading message list for %s[%s]"% (
|
||||||
self.getnicename(repos),
|
self.getnicename(repos),
|
||||||
folder))
|
folder))
|
||||||
|
|
||||||
@ -389,7 +400,8 @@ class UIBase(object):
|
|||||||
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."""
|
||||||
|
|
||||||
conn = None
|
conn = None
|
||||||
self._msg("%s repository '%s': type '%s'" % (type, repository.name,
|
self._msg("%s repository '%s': type '%s'" % (type, repository.name,
|
||||||
self.getnicename(repository)))
|
self.getnicename(repository)))
|
||||||
@ -440,8 +452,9 @@ class UIBase(object):
|
|||||||
repository.imapserver.close()
|
repository.imapserver.close()
|
||||||
|
|
||||||
def savemessage(self, debugtype, uid, flags, folder):
|
def savemessage(self, debugtype, uid, flags, folder):
|
||||||
"""Output a log line stating that we save a msg"""
|
"""Output a log line stating that we save a msg."""
|
||||||
self.debug(debugtype, "Write mail '%s:%d' with flags %s" %
|
|
||||||
|
self.debug(debugtype, u"Write mail '%s:%d' with flags %s"%
|
||||||
(folder, uid, repr(flags)))
|
(folder, uid, repr(flags)))
|
||||||
|
|
||||||
################################################## Threads
|
################################################## Threads
|
||||||
@ -461,42 +474,46 @@ class UIBase(object):
|
|||||||
del self.debugmessages[thread]
|
del self.debugmessages[thread]
|
||||||
|
|
||||||
def getThreadExceptionString(self, thread):
|
def getThreadExceptionString(self, thread):
|
||||||
message = "Thread '%s' terminated with exception:\n%s" % \
|
message = u"Thread '%s' terminated with exception:\n%s"% \
|
||||||
(thread.getName(), thread.exit_stacktrace)
|
(thread.getName(), thread.exit_stacktrace)
|
||||||
message += "\n" + self.getThreadDebugLog(thread)
|
message += u"\n" + self.getThreadDebugLog(thread)
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def threadException(self, thread):
|
def threadException(self, thread):
|
||||||
"""Called when a thread has terminated with an exception.
|
"""Called when a thread has terminated with an exception.
|
||||||
The argument is the ExitNotifyThread that has so terminated."""
|
The argument is the ExitNotifyThread that has so terminated."""
|
||||||
|
|
||||||
self.warn(self.getThreadExceptionString(thread))
|
self.warn(self.getThreadExceptionString(thread))
|
||||||
self.delThreadDebugLog(thread)
|
self.delThreadDebugLog(thread)
|
||||||
self.terminate(100)
|
self.terminate(100)
|
||||||
|
|
||||||
def terminate(self, exitstatus = 0, errortitle = None, errormsg = None):
|
def terminate(self, exitstatus = 0, errortitle = None, errormsg = None):
|
||||||
"""Called to terminate the application."""
|
"""Called to terminate the application."""
|
||||||
|
|
||||||
#print any exceptions that have occurred over the run
|
#print any exceptions that have occurred over the run
|
||||||
if not self.exc_queue.empty():
|
if not self.exc_queue.empty():
|
||||||
self.warn("ERROR: Exceptions occurred during the run!")
|
self.warn(u"ERROR: Exceptions occurred during the run!")
|
||||||
while not self.exc_queue.empty():
|
while not self.exc_queue.empty():
|
||||||
msg, exc, exc_traceback = self.exc_queue.get()
|
msg, exc, exc_traceback = self.exc_queue.get()
|
||||||
if msg:
|
if msg:
|
||||||
self.warn("ERROR: %s\n %s" % (msg, exc))
|
self.warn(u"ERROR: %s\n %s"% (msg, exc))
|
||||||
else:
|
else:
|
||||||
self.warn("ERROR: %s" % (exc))
|
self.warn(u"ERROR: %s"% (exc))
|
||||||
if exc_traceback:
|
if exc_traceback:
|
||||||
self.warn("\nTraceback:\n%s" %"".join(
|
self.warn(u"\nTraceback:\n%s"% "".join(
|
||||||
traceback.format_tb(exc_traceback)))
|
traceback.format_tb(exc_traceback)))
|
||||||
|
|
||||||
if errormsg and errortitle:
|
if errormsg and errortitle:
|
||||||
self.warn('ERROR: %s\n\n%s\n'%(errortitle, errormsg))
|
self.warn(u'ERROR: %s\n\n%s\n'% (errortitle, errormsg))
|
||||||
elif errormsg:
|
elif errormsg:
|
||||||
self.warn('%s\n' % errormsg)
|
self.warn(u'%s\n' % errormsg)
|
||||||
sys.exit(exitstatus)
|
sys.exit(exitstatus)
|
||||||
|
|
||||||
def threadExited(self, thread):
|
def threadExited(self, thread):
|
||||||
"""Called when a thread has exited normally. Many UIs will
|
"""Called when a thread has exited normally.
|
||||||
just ignore this."""
|
|
||||||
|
Many UIs will just ignore this."""
|
||||||
|
|
||||||
self.delThreadDebugLog(thread)
|
self.delThreadDebugLog(thread)
|
||||||
self.unregisterthread(thread)
|
self.unregisterthread(thread)
|
||||||
|
|
||||||
@ -518,6 +535,7 @@ class UIBase(object):
|
|||||||
:returns: 0/False if timeout expired, 1/2/True if there is a
|
:returns: 0/False if timeout expired, 1/2/True if there is a
|
||||||
request to cancel the timer.
|
request to cancel the timer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
abortsleep = False
|
abortsleep = False
|
||||||
while sleepsecs > 0 and not abortsleep:
|
while sleepsecs > 0 and not abortsleep:
|
||||||
if account.get_abort_event():
|
if account.get_abort_event():
|
||||||
@ -538,6 +556,7 @@ class UIBase(object):
|
|||||||
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.debug("Next refresh in %.1f minutes" % (
|
self.logger.debug("Next refresh in %.1f minutes" % (
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# Locking debugging code -- temporary
|
# Locking debugging code -- temporary
|
||||||
# Copyright (C) 2003 John Goerzen
|
# Copyright (C) 2003-2015 John Goerzen & contributors
|
||||||
# <jgoerzen@complete.org>
|
|
||||||
#
|
#
|
||||||
# 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
|
||||||
|
Loading…
Reference in New Issue
Block a user