Merge branch 'next' of github.com:OfflineIMAP/offlineimap into next

This commit is contained in:
Eygene Ryabinkin 2015-01-18 11:08:23 +03:00
commit 9b911faa58
19 changed files with 513 additions and 343 deletions

10
COPYING
View File

@ -65,7 +65,7 @@ patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
@ -120,7 +120,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
@ -178,7 +178,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
@ -235,7 +235,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
@ -288,7 +288,7 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest

View File

@ -10,10 +10,22 @@ Eygene Ryabinkin
email: rea at freebsd.org
github: konvpalto
Sebastian Spaeth
email: sebastian at sspaeth.de
github: spaetz
Nicolas Sebrecht
email: nicolas.s-dev at laposte.net
github: nicolas33
MAILING LIST MAINTAINERS
========================
Eygene Ryabinkin
email: rea at freebsd.org
Sebastian Spaeth
email: sebastian at sspaeth.de
github: spaetz
Nicolas Sebrecht
email: nicolas.s-dev at laposte.net

View File

@ -13,57 +13,6 @@ This document contains assorted guidelines for programmers that want
to hack OfflineIMAP.
------------------
Exception handling
------------------
OfflineIMAP on many occasions re-raises various exceptions and often
changes exception type to `OfflineImapError`. This is not a problem
per se, but you must always remember that we need to preserve original
tracebacks. This is not hard if you follow these simple rules.
For re-raising original exceptions, just use::
raise
from inside your exception handling code.
If you need to change exception type, or its argument, or whatever,
use this three-argument form::
raise YourExceptionClass(argum, ents), None, sys.exc_info()[2]
In this form, you're creating an instance of new exception, so ``raise``
will deduce its ``type`` and ``value`` parameters from the first argument,
thus the second expression passed to ``raise`` is always ``None``.
And the third one is the traceback object obtained from the thread-safe
``exc_info()`` function.
In fact, if you hadn't already imported the whole ``sys`` module, it will
be better to import just ``exc_info()``::
from sys import exc_info
and raise like this::
raise YourExceptionClass(argum, ents), None, exc_info()[2]
since this is the historically-preferred style in the OfflineIMAP code.
.. -*- coding: utf-8 -*-
.. _OfflineIMAP: https://github.com/OfflineIMAP/offlineimap
.. _OLI_git_repo: git://github.com/OfflineIMAP/offlineimap.git
=================================
Coding guidelines for OfflineIMAP
=================================
.. contents::
.. .. sectnum::
This document contains assorted guidelines for programmers that want
to hack OfflineIMAP.
------------------
Exception handling
------------------

View File

@ -23,7 +23,8 @@ from offlineimap import __version__,__author__
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo']
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest',
'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.viewcode']
autoclass_content = "both"
# Add any paths that contain templates here, relative to this directory.

File diff suppressed because it is too large Load Diff

View File

@ -33,15 +33,21 @@ except:
# FIXME: spaghetti code alert!
def getaccountlist(customconfig):
# Account names in a list.
return customconfig.getsectionlist('Account')
# FIXME: spaghetti code alert!
def AccountListGenerator(customconfig):
"""Returns a list of instanciated Account class, one per account name."""
return [Account(customconfig, accountname)
for accountname in getaccountlist(customconfig)]
# FIXME: spaghetti code alert!
def AccountHashGenerator(customconfig):
"""Returns a dict of instanciated Account class with the account name as
key."""
retval = {}
for item in AccountListGenerator(customconfig):
retval[item.getname()] = item
@ -54,9 +60,10 @@ class Account(CustomConfig.ConfigHelperMixin):
Most of the time you will actually want to use the derived
:class:`accounts.SyncableAccount` which contains all functions used
for syncing an account."""
#signal gets set when we should stop looping
# Signal gets set when we should stop looping.
abort_soon_signal = Event()
#signal gets set on CTRL-C/SIGTERM
# Signal gets set on CTRL-C/SIGTERM.
abort_NOW_signal = Event()
def __init__(self, config, name):
@ -66,6 +73,7 @@ class Account(CustomConfig.ConfigHelperMixin):
:param name: A string denoting the name of the Account
as configured"""
self.config = config
self.name = name
self.metadatadir = config.getmetadatadir()
@ -257,8 +265,8 @@ class SyncableAccount(Account):
raise
self.ui.error(e, exc_info()[2])
except Exception as e:
self.ui.error(e, exc_info()[2], msg = "While attempting to sync"
" account '%s'" % self)
self.ui.error(e, exc_info()[2], msg="While attempting to sync"
" account '%s'"% self)
else:
# after success sync, reset the looping counter to 3
if self.refreshperiod:
@ -483,3 +491,7 @@ def syncfolder(account, remotefolder, quick):
ui.error(e, msg = "ERROR in syncfolder for %s folder %s: %s" % \
(account, remotefolder.getvisiblename(),
traceback.format_exc()))
finally:
for folder in ["statusfolder", "localfolder", "remotefolder"]:
if folder in locals():
locals()[folder].dropmessagelistcache()

View File

@ -247,6 +247,9 @@ class BaseFolder(object):
raise NotImplementedError
def dropmessagelistcache(self):
raise NotImplementedException
def getmessagelist(self):
"""Gets the current message list.
You must call cachemessagelist() before calling this function!"""
@ -438,6 +441,11 @@ class BaseFolder(object):
- headername: name of the header to add
- headervalue: value of the header to add
.. note::
The following documentation will not get displayed correctly after being
processed by Sphinx. View the source of this method to read it.
This has to deal with strange corner cases where the header is
missing or empty. Here are illustrations for all the cases,
showing where the header gets inserted and what the end result

View File

@ -40,6 +40,8 @@ CRLF = '\r\n'
class IMAPFolder(BaseFolder):
def __init__(self, imapserver, name, repository):
# FIXME: decide if unquoted name is from the responsability of the
# caller or not, but not both.
name = imaputil.dequote(name)
self.sep = imapserver.delim
super(IMAPFolder, self).__init__(name, repository)
@ -248,6 +250,8 @@ class IMAPFolder(BaseFolder):
rtime = imaplibutil.Internaldate2epoch(messagestr)
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime}
def dropmessagelistcache(self):
self.messagelist = None
# Interface from BaseFolder
def getmessagelist(self):

View File

@ -161,6 +161,8 @@ class LocalStatusFolder(BaseFolder):
self.readstatus(file)
file.close()
def dropmessagelistcache(self):
self.messagelist = None
def save(self):
"""Save changed data to disk. For this backend it is the same as saveall."""

View File

@ -203,6 +203,9 @@ class LocalStatusSQLiteFolder(BaseFolder):
self.messagelist[uid]['labels'] = labels
self.messagelist[uid]['mtime'] = row[2]
def dropmessagelistcache(self):
self.messagelist = None
# Interface from LocalStatusFolder
def save(self):
pass

View File

@ -220,6 +220,9 @@ class MaildirFolder(BaseFolder):
if self.messagelist is None:
self.messagelist = self._scanfolder()
def dropmessagelistcache(self):
self.messagelist = None
# Interface from BaseFolder
def getmessagelist(self):
return self.messagelist

View File

@ -125,6 +125,9 @@ class MappedIMAPFolder(IMAPFolder):
finally:
self.maplock.release()
def dropmessagelistcache(self):
self._mb.dropmessagelistcache()
# Interface from BaseFolder
def uidexists(self, ruid):
"""Checks if the (remote) UID exists in this Folder"""

View File

@ -40,6 +40,7 @@ class OfflineImap:
oi = OfflineImap()
oi.run()
"""
def run(self):
"""Parse the commandline and invoke everything"""
# next line also sets self.config and self.ui
@ -321,6 +322,8 @@ class OfflineImap:
pass
try:
# Honor CLI --account option, only.
# Accounts to sync are put into syncaccounts variable.
activeaccounts = self.config.get("general", "accounts")
if options.accounts:
activeaccounts = options.accounts

View File

@ -31,10 +31,10 @@ class LocalEval:
if path is not None:
# FIXME: limit opening files owned by current user with rights set
# to fixed mode 644.
file = open(path, 'r')
foo = open(path, 'r')
module = imp.load_module(
'<none>',
file,
foo,
path,
('', 'r', imp.PY_SOURCE))
for attr in dir(module):

View File

@ -39,6 +39,7 @@ class BaseRepository(CustomConfig.ConfigHelperMixin, object):
self.mapdir = os.path.join(self.uiddir, 'UIDMapping')
if not os.path.exists(self.mapdir):
os.mkdir(self.mapdir, 0o700)
# FIXME: self.uiddir variable name is lying about itself.
self.uiddir = os.path.join(self.uiddir, 'FolderValidity')
if not os.path.exists(self.uiddir):
os.mkdir(self.uiddir, 0o700)

View File

@ -1,5 +1,5 @@
# 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
@ -23,8 +23,8 @@ class GmailMaildirRepository(MaildirRepository):
def __init__(self, reposname, account):
"""Initialize a MaildirRepository object. Takes a path name
to the directory holding all the Maildir directories."""
super(GmailMaildirRepository, self).__init__(reposname, account)
super(GmailMaildirRepository, self).__init__(reposname, account)
def getfoldertype(self):
return GmailMaildirFolder

View File

@ -301,6 +301,8 @@ class IMAPRepository(BaseRepository):
return None
def getfolder(self, foldername):
"""Return instance of OfflineIMAP representative folder."""
return self.getfoldertype()(self.imapserver, foldername, self)
def getfoldertype(self):
@ -314,6 +316,8 @@ class IMAPRepository(BaseRepository):
self.folders = None
def getfolders(self):
"""Return a list of instances of OfflineIMAP representative folder."""
if self.folders != None:
return self.folders
retval = []
@ -326,13 +330,13 @@ class IMAPRepository(BaseRepository):
listresult = listfunction(directory = self.imapserver.reference)[1]
finally:
self.imapserver.releaseconnection(imapobj)
for string in listresult:
if string == None or \
(isinstance(string, basestring) and string == ''):
for s in listresult:
if s == None or \
(isinstance(s, basestring) and s == ''):
# Bug in imaplib: empty strings in results from
# literals. TODO: still relevant?
continue
flags, delim, name = imaputil.imapsplit(string)
flags, delim, name = imaputil.imapsplit(s)
flaglist = [x.lower() for x in imaputil.flagsplit(flags)]
if '\\noselect' in flaglist:
continue
@ -353,9 +357,8 @@ class IMAPRepository(BaseRepository):
self.ui.error(e, exc_info()[2],
'Invalid folderinclude:')
continue
retval.append(self.getfoldertype()(self.imapserver,
foldername,
self))
retval.append(self.getfoldertype()(
self.imapserver, foldername, self))
finally:
self.imapserver.releaseconnection(imapobj)

View File

@ -94,7 +94,8 @@ class LocalStatusRepository(BaseRepository):
self.forgetfolders()
def getfolder(self, foldername):
"""Return the Folder() object for a foldername"""
"""Return the Folder() object for a foldername."""
if foldername in self._folders:
return self._folders[foldername]

View File

@ -170,8 +170,8 @@ class MaildirRepository(BaseRepository):
self.debug(" skip this entry (not a directory)")
# Not a directory -- not a folder.
continue
# extension can be None.
if extension:
# extension can be None which fails.
foldername = os.path.join(extension, dirname)
else:
foldername = dirname