Merge branch 'next'

This commit is contained in:
Nicolas Sebrecht 2012-09-12 19:48:19 +02:00
commit a73b4b3465
19 changed files with 177 additions and 104 deletions

View File

@ -4,17 +4,35 @@ ChangeLog
:website: http://offlineimap.org :website: http://offlineimap.org
WIP (add new stuff for the next release) WIP (add new stuff for the next release)
======================================== ========================================
OfflineIMAP v6.5.5-rc1 (2012-09-05)
===================================
* Bump version number
OfflineIMAP v6.5.5-rc1 (2012-09-05)
===================================
* Don't create folders if readonly is enabled.
* Learn to deal with readonly folders to properly detect this condition and act
accordingly. One example is Gmail's "Chats" folder that is read-only,
but contains logs of the quick chats. (E. Ryabinkin)
* Fix str.format() calls for Python 2.6 (D. Logie)
* Remove APPENDUID hack, previously introduced to fix Gmail, no longer
necessary, it might have been breaking things. (J. Wiegley)
* Improve regex that could lead to 'NoneType' object has no attribute 'group'
(D. Franke)
* Improved error throwing on repository misconfiguration
OfflineIMAP v6.5.4 (2012-06-02) OfflineIMAP v6.5.4 (2012-06-02)
================================= ===============================
* bump bundled imaplib2 library 2.29 --> 2.33 * bump bundled imaplib2 library 2.29 --> 2.33
* Actually perform the SSL fingerprint check (reported by J. Cook) * Actually perform the SSL fingerprint check (reported by J. Cook)
* Curses UI, don't use colors after we shut down curses already (C.Höger) * Curses UI, don't use colors after we shut down curses already (C.Höger)
* Document that '%' needs encoding as '%%' in *.conf * Document that '%' needs encoding as '%%' in configuration files.
* Fix crash when IMAP.quickchanged() led to an Error (reported by sharat87) * Fix crash when IMAP.quickchanged() led to an Error (reported by sharat87)
* Implement the createfolders setting to disable folder propagation (see docs) * Implement the createfolders setting to disable folder propagation (see docs)
@ -60,7 +78,7 @@ OfflineIMAP v6.5.3 (2012-04-02)
* Improve compatability of the curses UI with python 2.6 * Improve compatability of the curses UI with python 2.6
OfflineIMAP v6.5.2.1 (2012-04-04) OfflineIMAP v6.5.2.1 (2012-04-04)
===================================== =================================
* Fix python2.6 compatibility with the TTYUI backend (crash) * Fix python2.6 compatibility with the TTYUI backend (crash)
@ -116,7 +134,7 @@ Smallish bug fixes that deserve to be put out.
* Add filter information to the filter list in --info output * Add filter information to the filter list in --info output
OfflineIMAP v6.5.1.1 (2012-01-07) - "Das machine control is nicht fur gerfinger-poken und mittengrabben" OfflineIMAP v6.5.1.1 (2012-01-07) - "Das machine control is nicht fur gerfinger-poken und mittengrabben"
================================================================================================================== ========================================================================================================
Blinkenlights UI 6.5.0 regression fixes only. Blinkenlights UI 6.5.0 regression fixes only.

19
MAINTAINERS Normal file
View File

@ -0,0 +1,19 @@
Official maintainers
====================
Dmitrijs Ledkovs
email: xnox at debian.org
github: xnox
Eygene Ryabinkin
email: rea at freebsd.org
github: konvpalto
Nicolas Sebrecht
email: nicolas.s-dev at laposte.net
github: nicolas33
Sebastian Spaeth
email: sebastian at sspaeth.de
github: spaetz

12
README
View File

@ -10,7 +10,9 @@ messages on each computer, and changes you make one place will be visible on all
other systems. For instance, you can delete a message on your home computer, and other systems. For instance, you can delete a message on your home computer, and
it will appear deleted on your work computer as well. OfflineIMAP is also useful it will appear deleted on your work computer as well. OfflineIMAP is also useful
if you want to use a mail reader that does not have IMAP support, has poor IMAP if you want to use a mail reader that does not have IMAP support, has poor IMAP
support, or does not provide disconnected operation. It's homepage at http://offlineimap.org contains more information, source code, and online documentation. support, or does not provide disconnected operation. It's homepage at
http://offlineimap.org contains more information, source code, and online
documentation.
OfflineIMAP does not require additional python dependencies beyond python >=2.6 OfflineIMAP does not require additional python dependencies beyond python >=2.6
(although python-sqlite is strongly recommended). (although python-sqlite is strongly recommended).
@ -87,9 +89,13 @@ Mailing list & bug reporting
---------------------------- ----------------------------
The user discussion, development and all exciting stuff take place in the The user discussion, development and all exciting stuff take place in the
OfflineImap mailing list at http://lists.alioth.debian.org/mailman/listinfo/offlineimap-project. You do not need to subscribe to send emails. OfflineImap mailing list at
http://lists.alioth.debian.org/mailman/listinfo/offlineimap-project. You do not
need to subscribe to send emails.
Bugs, issues and contributions should be reported to the mailing list. Bugs can also be reported in the issue tracker at https://github.com/spaetz/offlineimap/issues. Bugs, issues and contributions should be reported to the mailing list. Bugs can
also be reported in the issue tracker at
https://github.com/OfflineIMAP/offlineimap/issues.
Configuration Examples Configuration Examples
====================== ======================

View File

@ -1,6 +1,6 @@
.. -*- coding: utf-8 -*- .. -*- coding: utf-8 -*-
.. _OfflineIMAP: https://github.com/spaetz/offlineimap .. _OfflineIMAP: https://github.com/OfflineIMAP/offlineimap
.. _OLI_git_repo: git://github.com/spaetz/offlineimap.git .. _OLI_git_repo: git://github.com/OfflineIMAP/offlineimap.git
============ ============
Installation Installation
@ -37,29 +37,40 @@ In order to use `OfflineIMAP`_, you need to have these conditions satisfied:
Installation Installation
------------ ------------
Installing OfflineImap should usually be quite easy, as you can simply unpack and run OfflineImap in place if you wish to do so. There are a number of options though: Installing OfflineImap should usually be quite easy, as you can simply unpack
and run OfflineImap in place if you wish to do so. There are a number of options
though:
#. system-wide :ref:`installation via your distribution package manager <inst_pkg_man>` #. system-wide :ref:`installation via your distribution package manager <inst_pkg_man>`
#. system-wide or single user :ref:`installation from the source package <inst_src_tar>` #. system-wide or single user :ref:`installation from the source package <inst_src_tar>`
#. system-wide or single user :ref:`installation from a git checkout <inst_git>` #. system-wide or single user :ref:`installation from a git checkout <inst_git>`
Having installed OfflineImap, you will need to configure it, to be actually useful. Please check the :ref:`Configuration` section in the :doc:`MANUAL` for more information on the configuration step. Having installed OfflineImap, you will need to configure it, to be actually
useful. Please check the :ref:`Configuration` section in the :doc:`MANUAL` for
more information on the configuration step.
.. _inst_pkg_man: .. _inst_pkg_man:
System-Wide Installation via distribution System-Wide Installation via distribution
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The easiest way to install OfflineIMAP is via your distribution's package manager. OfflineImap is available under the name `offlineimap` in most Linux and BSD distributions. The easiest way to install OfflineIMAP is via your distribution's package
manager. OfflineImap is available under the name `offlineimap` in most Linux and
BSD distributions.
.. _inst_src_tar: .. _inst_src_tar:
Installation from source package Installation from source package
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Download the latest source archive from our `download page <https://github.com/spaetz/offlineimap/downloads>`_. Simply click the "Download as .zip" or "Download as .tar.gz" buttons to get the latest "stable" code from the master branch. If you prefer command line, you will want to use:
wget https://github.com/spaetz/offlineimap/tarball/master
Unpack and continue with the :ref:`system-wide installation <system_wide_inst>` or the :ref:`single-user installation <single_user_inst>` section. Download the latest source archive from our `download page
<https://github.com/spaetz/offlineimap/downloads>`_. Simply click the "Download
as .zip" or "Download as .tar.gz" buttons to get the latest "stable" code from
the master branch. If you prefer command line, you will want to use: wget
https://github.com/spaetz/offlineimap/tarball/master
Unpack and continue with the :ref:`system-wide installation <system_wide_inst>`
or the :ref:`single-user installation <single_user_inst>` section.
.. _inst_git: .. _inst_git:
@ -78,7 +89,9 @@ checkout a particular release like this::
cd offlineimap cd offlineimap
git checkout v6.5.2.1 git checkout v6.5.2.1
You have now a source tree available and proceed with either the :ref:`system-wide installation <system_wide_inst>` or the :ref:`single-user installation <single_user_inst>`. You have now a source tree available and proceed with either the
:ref:`system-wide installation <system_wide_inst>` or the :ref:`single-user
installation <single_user_inst>`.
.. _system_wide_inst: .. _system_wide_inst:

View File

@ -1,7 +1,7 @@
__all__ = ['OfflineImap'] __all__ = ['OfflineImap']
__productname__ = 'OfflineIMAP' __productname__ = 'OfflineIMAP'
__version__ = "6.5.4" __version__ = "6.5.5-rc2"
__copyright__ = "Copyright 2002-2012 John Goerzen & contributors" __copyright__ = "Copyright 2002-2012 John Goerzen & contributors"
__author__ = "John Goerzen" __author__ = "John Goerzen"
__author_email__= "john@complete.org" __author_email__= "john@complete.org"

View File

@ -217,13 +217,19 @@ class SyncableAccount(Account):
def syncrunner(self): def syncrunner(self):
self.ui.registerthread(self) self.ui.registerthread(self)
accountmetadata = self.getaccountmeta() try:
if not os.path.exists(accountmetadata): accountmetadata = self.getaccountmeta()
os.mkdir(accountmetadata, 0o700) if not os.path.exists(accountmetadata):
os.mkdir(accountmetadata, 0o700)
self.remoterepos = Repository(self, 'remote') self.remoterepos = Repository(self, 'remote')
self.localrepos = Repository(self, 'local') self.localrepos = Repository(self, 'local')
self.statusrepos = Repository(self, 'status') self.statusrepos = Repository(self, 'status')
except OfflineImapError as e:
self.ui.error(e, exc_info()[2])
if e.severity >= OfflineImapError.ERROR.CRITICAL:
raise
return
# Loop account sync if needed (bail out after 3 failures) # Loop account sync if needed (bail out after 3 failures)
looping = 3 looping = 3
@ -255,6 +261,12 @@ class SyncableAccount(Account):
if looping and self.sleeper() >= 2: if looping and self.sleeper() >= 2:
looping = 0 looping = 0
def get_local_folder(self, remotefolder):
"""Return the corresponding local folder for a given remotefolder"""
return self.localrepos.getfolder(
remotefolder.getvisiblename().
replace(self.remoterepos.getsep(), self.localrepos.getsep()))
def sync(self): def sync(self):
"""Synchronize the account once, then return """Synchronize the account once, then return
@ -289,7 +301,6 @@ class SyncableAccount(Account):
# folder delimiter etc) # folder delimiter etc)
remoterepos.getfolders() remoterepos.getfolders()
localrepos.getfolders() localrepos.getfolders()
statusrepos.getfolders()
remoterepos.sync_folder_structure(localrepos, statusrepos) remoterepos.sync_folder_structure(localrepos, statusrepos)
# replicate the folderstructure between REMOTE to LOCAL # replicate the folderstructure between REMOTE to LOCAL
@ -300,10 +311,16 @@ class SyncableAccount(Account):
for remotefolder in remoterepos.getfolders(): for remotefolder in remoterepos.getfolders():
# check for CTRL-C or SIGTERM # check for CTRL-C or SIGTERM
if Account.abort_NOW_signal.is_set(): break if Account.abort_NOW_signal.is_set(): break
if not remotefolder.sync_this: if not remotefolder.sync_this:
self.ui.debug('', "Not syncing filtered remote folder '%s'" self.ui.debug('', "Not syncing filtered folder '%s'"
"[%s]" % (remotefolder, remoterepos)) "[%s]" % (remotefolder, remoterepos))
continue # Filtered out remote folder continue # Ignore filtered folder
localfolder = self.get_local_folder(remotefolder)
if not localfolder.sync_this:
self.ui.debug('', "Not syncing filtered folder '%s'"
"[%s]" % (localfolder, localfolder.repository))
continue # Ignore filtered folder
thread = InstanceLimitedThread(\ thread = InstanceLimitedThread(\
instancename = 'FOLDER_' + self.remoterepos.getname(), instancename = 'FOLDER_' + self.remoterepos.getname(),
target = syncfolder, target = syncfolder,
@ -367,17 +384,8 @@ def syncfolder(account, remotefolder, quick):
ui.registerthread(account) ui.registerthread(account)
try: try:
# Load local folder. # Load local folder.
localfolder = localrepos.\ localfolder = account.get_local_folder(remotefolder)
getfolder(remotefolder.getvisiblename().\
replace(remoterepos.getsep(), localrepos.getsep()))
#Filtered folders on the remote side will not invoke this
#function, but we need to NOOP if the local folder is filtered
#out too:
if not localfolder.sync_this:
ui.debug('', "Not syncing filtered local folder '%s'" \
% localfolder)
return
# Write the mailboxes # Write the mailboxes
mbnames.add(account.name, localfolder.getname()) mbnames.add(account.name, localfolder.getname())
@ -457,15 +465,8 @@ def syncfolder(account, remotefolder, quick):
if e.severity > OfflineImapError.ERROR.FOLDER: if e.severity > OfflineImapError.ERROR.FOLDER:
raise raise
else: else:
#if the initial localfolder assignement bailed out, the localfolder var will not be available, so we need
ui.error(e, exc_info()[2], msg = "Aborting sync, folder '%s' " ui.error(e, exc_info()[2], msg = "Aborting sync, folder '%s' "
"[acc: '%s']" % ( "[acc: '%s']" % (localfolder, account))
remotefolder.getvisiblename().\
replace(remoterepos.getsep(), localrepos.getsep()),
account))
# we reconstruct foldername above rather than using
# localfolder, as the localfolder var is not
# available if assignment fails.
except Exception as e: except Exception as e:
ui.error(e, msg = "ERROR in syncfolder for %s folder %s: %s" % \ ui.error(e, msg = "ERROR in syncfolder for %s folder %s: %s" % \
(account, remotefolder.getvisiblename(), (account, remotefolder.getvisiblename(),

View File

@ -31,9 +31,12 @@ class BaseFolder(object):
:para name: Path & name of folder minus root or reference :para name: Path & name of folder minus root or reference
:para repository: Repository() in which the folder is. :para repository: Repository() in which the folder is.
""" """
self.sync_this = True
"""Should this folder be included in syncing?"""
self.ui = getglobalui() self.ui = getglobalui()
"""Should this folder be included in syncing?"""
self._sync_this = repository.should_sync_folder(name)
if not self._sync_this:
self.ui.debug('', "Filtering out '%s'[%s] due to folderfilter" \
% (name, repository))
# Top level dir name is always '' # Top level dir name is always ''
self.name = name if not name == self.getsep() else '' self.name = name if not name == self.getsep() else ''
self.repository = repository self.repository = repository
@ -57,6 +60,11 @@ class BaseFolder(object):
"""Account name as string""" """Account name as string"""
return self.repository.accountname return self.repository.accountname
@property
def sync_this(self):
"""Should this folder be synced or is it e.g. filtered out?"""
return self._sync_this
def suggeststhreads(self): def suggeststhreads(self):
"""Returns true if this folder suggests using threads for actions; """Returns true if this folder suggests using threads for actions;
false otherwise. Probably only IMAP will return true.""" false otherwise. Probably only IMAP will return true."""
@ -386,7 +394,7 @@ class BaseFolder(object):
self.getmessageuidlist()) self.getmessageuidlist())
num_to_copy = len(copylist) num_to_copy = len(copylist)
if num_to_copy and self.repository.account.dryrun: if num_to_copy and self.repository.account.dryrun:
self.ui.info("[DRYRUN] Copy {} messages from {}[{}] to {}".format( self.ui.info("[DRYRUN] Copy {0} messages from {1}[{2}] to {3}".format(
num_to_copy, self, self.repository, dstfolder.repository)) num_to_copy, self, self.repository, dstfolder.repository))
return return
for num, uid in enumerate(copylist): for num, uid in enumerate(copylist):

View File

@ -575,9 +575,8 @@ class IMAPFolder(BaseFolder):
(typ,dat) = imapobj.check() (typ,dat) = imapobj.check()
assert(typ == 'OK') assert(typ == 'OK')
# get the new UID. Test for APPENDUID response even if the # get the new UID, do we use UIDPLUS?
# server claims to not support it, as e.g. Gmail does :-( if use_uidplus:
if use_uidplus or imapobj._get_untagged_response('APPENDUID', True):
# get new UID from the APPENDUID response, it could look # get new UID from the APPENDUID response, it could look
# like OK [APPENDUID 38505 3955] APPEND completed with # like OK [APPENDUID 38505 3955] APPEND completed with
# 38505 bein folder UIDvalidity and 3955 the new UID. # 38505 bein folder UIDvalidity and 3955 the new UID.
@ -585,7 +584,7 @@ class IMAPFolder(BaseFolder):
# often seems to return [None], even though we have # often seems to return [None], even though we have
# data. TODO # data. TODO
resp = imapobj._get_untagged_response('APPENDUID') resp = imapobj._get_untagged_response('APPENDUID')
if resp == [None]: if resp == [None] or resp is None:
self.ui.warn("Server supports UIDPLUS but got no APPENDUID " self.ui.warn("Server supports UIDPLUS but got no APPENDUID "
"appending a message.") "appending a message.")
return 0 return 0

View File

@ -85,8 +85,7 @@ class LocalStatusFolder(BaseFolder):
file.close() file.close()
def save(self): def save(self):
self.savelock.acquire() with self.savelock:
try:
file = open(self.filename + ".tmp", "wt") file = open(self.filename + ".tmp", "wt")
file.write(magicline + "\n") file.write(magicline + "\n")
for msg in self.messagelist.values(): for msg in self.messagelist.values():
@ -104,9 +103,6 @@ class LocalStatusFolder(BaseFolder):
os.fsync(fd) os.fsync(fd)
os.close(fd) os.close(fd)
finally:
self.savelock.release()
def getmessagelist(self): def getmessagelist(self):
return self.messagelist return self.messagelist

View File

@ -15,6 +15,7 @@
# 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 threading import Lock from threading import Lock
from offlineimap import OfflineImapError
from .IMAP import IMAPFolder from .IMAP import IMAPFolder
import os.path import os.path

View File

@ -23,7 +23,7 @@ from offlineimap.ui import getglobalui
# find the first quote in a string # find the first quote in a string
quotere = re.compile( quotere = re.compile(
r"""(?P<quote>"(?:\\"|[^"])*") # Quote, possibly containing encoded r"""(?P<quote>"[^\"\\]*(?:\\"|[^"])*") # Quote, possibly containing encoded
# quotation mark # quotation mark
\s*(?P<rest>.*)$ # Whitespace & remainder of string""", \s*(?P<rest>.*)$ # Whitespace & remainder of string""",
re.VERBOSE) re.VERBOSE)

View File

@ -129,12 +129,17 @@ class BaseRepository(CustomConfig.ConfigHelperMixin, object):
def getsep(self): def getsep(self):
raise NotImplementedError raise NotImplementedError
def should_sync_folder(self, fname):
"""Should this folder be synced?"""
return fname in self.folderincludes or self.folderfilter(fname)
def get_create_folders(self): def get_create_folders(self):
"""Is folder creation enabled on this repository? """Is folder creation enabled on this repository?
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 self._readonly or self.getconfboolean('createfolders', True) return (not self._readonly) and \
self.getconfboolean('createfolders', True)
def makefolder(self, foldername): def makefolder(self, foldername):
"""Create a new folder""" """Create a new folder"""
@ -201,7 +206,7 @@ class BaseRepository(CustomConfig.ConfigHelperMixin, object):
# Does nametrans back&forth lead to identical names? # Does nametrans back&forth lead to identical names?
# 1) would src repo filter out the new folder name? In this # 1) would src repo filter out the new folder name? In this
# case don't create it on it: # case don't create it on it:
if not self.folderfilter(dst_name_t): if not self.should_sync_folder(dst_name_t):
self.ui.debug('', "Not creating folder '%s' (repository '%s" self.ui.debug('', "Not creating folder '%s' (repository '%s"
"') as it would be filtered out on that repository." % "') as it would be filtered out on that repository." %
(dst_name_t, self)) (dst_name_t, self))

View File

@ -287,11 +287,6 @@ class IMAPRepository(BaseRepository):
foldername = imaputil.dequote(name) foldername = imaputil.dequote(name)
retval.append(self.getfoldertype()(self.imapserver, foldername, retval.append(self.getfoldertype()(self.imapserver, foldername,
self)) self))
# filter out the folder?
if not self.folderfilter(foldername):
self.ui.debug('imap', "Filtering out '%s'[%s] due to folderfilt"
"er" % (foldername, self))
retval[-1].sync_this = False
# Add all folderincludes # Add all folderincludes
if len(self.folderincludes): if len(self.folderincludes):
imapobj = self.imapserver.acquireconnection() imapobj = self.imapserver.acquireconnection()

View File

@ -43,8 +43,8 @@ class LocalStatusRepository(BaseRepository):
if not os.path.exists(self.root): if not os.path.exists(self.root):
os.mkdir(self.root, 0o700) os.mkdir(self.root, 0o700)
# self._folders is a list of LocalStatusFolders() # self._folders is a dict of name:LocalStatusFolders()
self._folders = None self._folders = {}
def getsep(self): def getsep(self):
return '.' return '.'
@ -79,23 +79,27 @@ class LocalStatusRepository(BaseRepository):
file.close() file.close()
os.rename(filename + ".tmp", filename) os.rename(filename + ".tmp", filename)
# Invalidate the cache. # Invalidate the cache.
self._folders = None self._folders = {}
def getfolder(self, foldername): def getfolder(self, foldername):
"""Return the Folder() object for a foldername""" """Return the Folder() object for a foldername"""
return self.LocalStatusFolderClass(foldername, self) if foldername in self._folders:
return self._folders[foldername]
folder = self.LocalStatusFolderClass(foldername, self)
self._folders[foldername] = folder
return folder
def getfolders(self): def getfolders(self):
"""Returns a list of all cached folders.""" """Returns a list of all cached folders.
if self._folders != None:
return self._folders
self._folders = [] Does nothing for this backend. We mangle the folder file names
for folder in os.listdir(self.root): (see getfolderfilename) so we can not derive folder names from
self._folders.append(self.getfolder(folder)) the file names that we have available. TODO: need to store a
return self._folders list of folder names somehow?"""
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 = None self._folders = {}

View File

@ -181,11 +181,6 @@ class MaildirRepository(BaseRepository):
foldername, foldername,
self.getsep(), self.getsep(),
self)) self))
# filter out the folder?
if not self.folderfilter(foldername):
self.debug("Filtering out '%s'[%s] due to folderfilt"
"er" % (foldername, self))
retval[-1].sync_this = False
if self.getsep() == '/' and dirname != '': if self.getsep() == '/' and dirname != '':
# Recursively check sub-directories for folders too. # Recursively check sub-directories for folders too.

View File

@ -15,10 +15,17 @@
# 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
try:
from configparser import NoSectionError
except ImportError: #python2
from ConfigParser import NoSectionError
from offlineimap.repository.IMAP import IMAPRepository, MappedIMAPRepository from offlineimap.repository.IMAP import IMAPRepository, MappedIMAPRepository
from offlineimap.repository.Gmail import GmailRepository from offlineimap.repository.Gmail import GmailRepository
from offlineimap.repository.Maildir import MaildirRepository from offlineimap.repository.Maildir import MaildirRepository
from offlineimap.repository.LocalStatus import LocalStatusRepository from offlineimap.repository.LocalStatus import LocalStatusRepository
from offlineimap.error import OfflineImapError
class Repository(object): class Repository(object):
"""Abstract class that returns the correct Repository type """Abstract class that returns the correct Repository type
@ -47,17 +54,26 @@ class Repository(object):
return LocalStatusRepository(name, account) return LocalStatusRepository(name, account)
else: else:
raise ValueError("Request type %s not supported" % reqtype) errstr = "Repository type %s not supported" % reqtype
raise OfflineImapError(errstr, OfflineImapError.ERROR.REPO)
# Get repository type
config = account.getconfig() config = account.getconfig()
repostype = config.get('Repository ' + name, 'type').strip() try:
repostype = config.get('Repository ' + name, 'type').strip()
except NoSectionError as e:
errstr = ("Could not find section '%s' in configuration. Required "
"for account '%s'." % ('Repository %s' % name, account))
raise OfflineImapError(errstr, OfflineImapError.ERROR.REPO)
try: try:
repo = typemap[repostype] repo = typemap[repostype]
except KeyError: except KeyError:
raise ValueError("'%s' repository not supported for %s repositories" errstr = "'%s' repository not supported for '%s' repositories." \
"." % (repostype, reqtype)) % (repostype, reqtype)
return repo(name, account) raise OfflineImapError(errstr, OfflineImapError.ERROR.REPO)
return repo(name, account)
def __init__(self, account, reqtype): def __init__(self, account, reqtype):
"""Load the correct Repository type and return that. The """Load the correct Repository type and return that. The

View File

@ -88,7 +88,6 @@ class UIBase(object):
def setlogfile(self, logfile): def setlogfile(self, logfile):
"""Create file handler which logs to file""" """Create file handler which logs to file"""
fh = logging.FileHandler(logfile, 'at') fh = logging.FileHandler(logfile, 'at')
#fh.setLevel(logging.DEBUG)
file_formatter = logging.Formatter("%(asctime)s %(levelname)s: " file_formatter = logging.Formatter("%(asctime)s %(levelname)s: "
"%(message)s", '%Y-%m-%d %H:%M:%S') "%(message)s", '%Y-%m-%d %H:%M:%S')
fh.setFormatter(file_formatter) fh.setFormatter(file_formatter)
@ -98,9 +97,7 @@ class UIBase(object):
msg = "OfflineImap %s starting...\n Python: %s Platform: %s\n "\ msg = "OfflineImap %s starting...\n Python: %s Platform: %s\n "\
"Args: %s" % (offlineimap.__version__, p_ver, sys.platform, "Args: %s" % (offlineimap.__version__, p_ver, sys.platform,
" ".join(sys.argv)) " ".join(sys.argv))
record = logging.LogRecord('OfflineImap', logging.INFO, __file__, self.logger.info(msg)
None, msg, None, None)
fh.emit(record)
def _msg(self, msg): def _msg(self, msg):
"""Display a message.""" """Display a message."""
@ -301,7 +298,7 @@ class UIBase(object):
def makefolder(self, repo, foldername): def makefolder(self, repo, foldername):
"""Called when a folder is created""" """Called when a folder is created"""
prefix = "[DRYRUN] " if self.dryrun else "" prefix = "[DRYRUN] " if self.dryrun else ""
self.info("{}Creating folder {}[{}]".format( self.info("{0}Creating folder {1}[{2}]".format(
prefix, foldername, repo)) prefix, foldername, repo))
def syncingfolder(self, srcrepos, srcfolder, destrepos, destfolder): def syncingfolder(self, srcrepos, srcfolder, destrepos, destfolder):
@ -346,7 +343,7 @@ class UIBase(object):
def deletingmessages(self, uidlist, destlist): def deletingmessages(self, uidlist, destlist):
ds = self.folderlist(destlist) ds = self.folderlist(destlist)
prefix = "[DRYRUN] " if self.dryrun else "" prefix = "[DRYRUN] " if self.dryrun else ""
self.info("{}Deleting {} messages ({}) in {}".format( self.info("{0}Deleting {1} messages ({2}) in {3}".format(
prefix, len(uidlist), prefix, len(uidlist),
offlineimap.imaputil.uid_sequence(uidlist), ds)) offlineimap.imaputil.uid_sequence(uidlist), ds))
@ -474,7 +471,7 @@ class UIBase(object):
def callhook(self, msg): def callhook(self, msg):
if self.dryrun: if self.dryrun:
self.info("[DRYRUN] {}".format(msg)) self.info("[DRYRUN] {0}".format(msg))
else: else:
self.info(msg) self.info(msg)

View File

@ -127,7 +127,7 @@ class OLITestLib():
reponame: All on `reponame` or all IMAP-type repositories if None""" reponame: All on `reponame` or all IMAP-type repositories if None"""
config = cls.get_default_config() config = cls.get_default_config()
if reponame: if reponame:
sections = ['Repository {}'.format(reponame)] sections = ['Repository {0}'.format(reponame)]
else: else:
sections = [r for r in config.sections() \ sections = [r for r in config.sections() \
if r.startswith('Repository')] if r.startswith('Repository')]
@ -162,8 +162,8 @@ class OLITestLib():
dirs = [d for d in dirs if d.startswith(b'INBOX.OLItest')] dirs = [d for d in dirs if d.startswith(b'INBOX.OLItest')]
for folder in dirs: for folder in dirs:
res_t, data = imapobj.delete(b'\"'+folder+b'\"') res_t, data = imapobj.delete(b'\"'+folder+b'\"')
assert res_t == 'OK', "Folder deletion of {} failed with error"\ assert res_t == 'OK', "Folder deletion of {0} failed with error"\
":\n{} {}".format(folder.decode('utf-8'), res_t, data) ":\n{1} {2}".format(folder.decode('utf-8'), res_t, data)
imapobj.logout() imapobj.logout()
@classmethod @classmethod
@ -197,7 +197,7 @@ class OLITestLib():
Use some default content if not given""" Use some default content if not given"""
assert cls.testdir != None assert cls.testdir != None
while True: # Loop till we found a unique filename while True: # Loop till we found a unique filename
mailfile = '{}:2,'.format(random.randint(0,999999999)) mailfile = '{0}:2,'.format(random.randint(0,999999999))
mailfilepath = os.path.join(cls.testdir, 'mail', mailfilepath = os.path.join(cls.testdir, 'mail',
folder, 'new', mailfile) folder, 'new', mailfile)
if not os.path.isfile(mailfilepath): if not os.path.isfile(mailfilepath):

View File

@ -67,7 +67,7 @@ class TestBasicFunctions(unittest.TestCase):
self.assertEqual(res, "") self.assertEqual(res, "")
boxes, mails = OLITestLib.count_maildir_mails('') boxes, mails = OLITestLib.count_maildir_mails('')
self.assertTrue((boxes, mails)==(0,0), msg="Expected 0 folders and 0 " self.assertTrue((boxes, mails)==(0,0), msg="Expected 0 folders and 0 "
"mails, but sync led to {} folders and {} mails".format( "mails, but sync led to {0} folders and {1} mails".format(
boxes, mails)) boxes, mails))
def test_02_createdir(self): def test_02_createdir(self):
@ -82,7 +82,7 @@ class TestBasicFunctions(unittest.TestCase):
self.assertEqual(res, "") self.assertEqual(res, "")
boxes, mails = OLITestLib.count_maildir_mails('') boxes, mails = OLITestLib.count_maildir_mails('')
self.assertTrue((boxes, mails)==(2,0), msg="Expected 2 folders and 0 " self.assertTrue((boxes, mails)==(2,0), msg="Expected 2 folders and 0 "
"mails, but sync led to {} folders and {} mails".format( "mails, but sync led to {0} folders and {1} mails".format(
boxes, mails)) boxes, mails))
def test_03_nametransmismatch(self): def test_03_nametransmismatch(self):
@ -101,7 +101,7 @@ class TestBasicFunctions(unittest.TestCase):
mismatch = "ERROR: INFINITE FOLDER CREATION DETECTED!" in res mismatch = "ERROR: INFINITE FOLDER CREATION DETECTED!" in res
self.assertEqual(mismatch, True, msg="Mismatching nametrans rules did " self.assertEqual(mismatch, True, msg="Mismatching nametrans rules did "
"NOT trigger an 'infinite folder generation' error. Output was:\n" "NOT trigger an 'infinite folder generation' error. Output was:\n"
"{}".format(res)) "{0}".format(res))
# Write out default config file again # Write out default config file again
OLITestLib.write_config_file() OLITestLib.write_config_file()
@ -121,12 +121,12 @@ class TestBasicFunctions(unittest.TestCase):
self.assertEqual(res, "") self.assertEqual(res, "")
boxes, mails = OLITestLib.count_maildir_mails('') boxes, mails = OLITestLib.count_maildir_mails('')
self.assertTrue((boxes, mails)==(1,1), msg="Expected 1 folders and 1 " self.assertTrue((boxes, mails)==(1,1), msg="Expected 1 folders and 1 "
"mails, but sync led to {} folders and {} mails".format( "mails, but sync led to {0} folders and {1} mails".format(
boxes, mails)) boxes, mails))
# The local Mail should have been assigned a proper UID now, check! # The local Mail should have been assigned a proper UID now, check!
uids = OLITestLib.get_maildir_uids('INBOX.OLItest') uids = OLITestLib.get_maildir_uids('INBOX.OLItest')
self.assertFalse (None in uids, msg = "All mails should have been "+ \ self.assertFalse (None in uids, msg = "All mails should have been "+ \
"assigned the IMAP's UID number, but {} messages had no valid ID "\ "assigned the IMAP's UID number, but {0} messages had no valid ID "\
.format(len([None for x in uids if x==None]))) .format(len([None for x in uids if x==None])))
def test_05_createfolders(self): def test_05_createfolders(self):