From fb3abaa6a50a69b0ae33900be5b8de818e8418d5 Mon Sep 17 00:00:00 2001 From: Nicolas Sebrecht Date: Sun, 24 Apr 2011 18:37:23 +0200 Subject: [PATCH 01/10] v6.3.2.2 Signed-off-by: Nicolas Sebrecht --- Changelog.maint.rst | 8 ++++++++ Makefile | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Changelog.maint.rst b/Changelog.maint.rst index 605ee8c..4371405 100644 --- a/Changelog.maint.rst +++ b/Changelog.maint.rst @@ -13,6 +13,14 @@ This is the Changelog of the maintenance branch. releases announces. +OfflineIMAP v6.3.2.2 (2011-04-24) +================================= + +Changes +------- + +* Improve traceback on some crashes. + OfflineIMAP v6.3.2.1 (2011-03-23) ================================= diff --git a/Makefile b/Makefile index 5970dd9..92ec040 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -VERSION=6.3.2.1 +VERSION=6.3.2.2 TARGZ=offlineimap_$(VERSION).tar.gz SHELL=/bin/bash RST2HTML=`type rst2html 2>/dev/null 2>&1 && echo rst2html || echo rst2html.py` From 50f0fbb42101c22e581bd583dcbd1080ce8b5713 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Mon, 2 May 2011 11:44:19 +0200 Subject: [PATCH 02/10] More detailed error output on corrupt UID mapping files This function will need much more "robustifying", but the very least we can do is to print the file name and line that are giving trouble. Signed-off-by: Sebastian Spaeth Signed-off-by: Nicolas Sebrecht --- Changelog.draft.rst | 2 ++ offlineimap/folder/UIDMaps.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Changelog.draft.rst b/Changelog.draft.rst index 8b599f9..2aee8d7 100644 --- a/Changelog.draft.rst +++ b/Changelog.draft.rst @@ -16,6 +16,8 @@ New Features Changes ------- +* Give more detailed error when encountering a corrupt UID mapping file. + Bug Fixes --------- diff --git a/offlineimap/folder/UIDMaps.py b/offlineimap/folder/UIDMaps.py index e7394b5..c77fd14 100644 --- a/offlineimap/folder/UIDMaps.py +++ b/offlineimap/folder/UIDMaps.py @@ -45,7 +45,11 @@ class MappingFolderMixIn: line = file.readline() if not len(line): break - line = line.strip() + try: + line = line.strip() + except ValueError: + raise Exception("Corrupt line '%s' in UID mapping file '%s'" \ + %(line, mapfilename)) (str1, str2) = line.split(':') loc = long(str1) rem = long(str2) From b1ff307674fb4dde80d8b63bdcdb3553561e9ae3 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Wed, 4 May 2011 19:44:01 +0200 Subject: [PATCH 03/10] Output more detailed error on corrupt LocalStatus When our LocalStatus cache is corrupt, ie e.g. it contains lines not in the form number:number, we would previously just raise a ValueError stating things like "too many values". In case we encounter clearly corrupt LocalStatus cache entries, clearly raise an exception stating the filename and the line, so that people can attempt to repair it. Signed-off-by: Sebastian Spaeth Signed-off-by: Nicolas Sebrecht --- offlineimap/folder/LocalStatus.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/offlineimap/folder/LocalStatus.py b/offlineimap/folder/LocalStatus.py index 50a0f55..3a0a16d 100644 --- a/offlineimap/folder/LocalStatus.py +++ b/offlineimap/folder/LocalStatus.py @@ -77,8 +77,13 @@ class LocalStatusFolder(BaseFolder): assert(line == magicline) for line in file.xreadlines(): line = line.strip() - uid, flags = line.split(':') - uid = long(uid) + try: + uid, flags = line.split(':') + uid = long(uid) + except ValueError, e: + errstr = "Corrupt line '%s' in cache file '%s'" % (line, self.filename) + self.ui.warn(errstr) + raise ValueError(errstr) flags = [x for x in flags] self.messagelist[uid] = {'uid': uid, 'flags': flags} file.close() From a40e5069080b337956bef039a9bfcb309910fd06 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Thu, 5 May 2011 11:15:51 +0200 Subject: [PATCH 04/10] Fix typo to force singlethreading in debug mode A typo prevented us from enforcing singlethreading mode when selecting debugging. Signed-off-by: Sebastian Spaeth Signed-off-by: Nicolas Sebrecht --- offlineimap/init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/offlineimap/init.py b/offlineimap/init.py index 8aebb23..9e23fdd 100644 --- a/offlineimap/init.py +++ b/offlineimap/init.py @@ -213,7 +213,7 @@ class OfflineImap: if not ('thread' in options.debugtype.split(',') \ and options.singlethreading): ui._msg("Debug mode: Forcing to singlethreaded.") - options.singlethreaded = True + options.singlethreading = True for type in options.debugtype.split(','): type = type.strip() From e8d303fa4a3876b2aa7c6dcac92a4c6635976294 Mon Sep 17 00:00:00 2001 From: Nicolas Sebrecht Date: Wed, 10 Aug 2011 21:48:54 +0200 Subject: [PATCH 05/10] v6.3.2.3 Signed-off-by: Nicolas Sebrecht --- Changelog.maint.rst | 16 ++++++++++++++++ Makefile | 2 +- offlineimap/__init__.py | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Changelog.maint.rst b/Changelog.maint.rst index 4371405..89db798 100644 --- a/Changelog.maint.rst +++ b/Changelog.maint.rst @@ -13,6 +13,21 @@ This is the Changelog of the maintenance branch. releases announces. +OfflineIMAP v6.3.2.3 (2011-08-10) +================================= + +Changes +------- + +* Output more detailed error on corrupt LocalStatus. +* More detailed error output on corrupt UID mapping files. + +Bug Fixes +--------- + +* Fix typo to force singlethreading in debug mode. + + OfflineIMAP v6.3.2.2 (2011-04-24) ================================= @@ -21,6 +36,7 @@ Changes * Improve traceback on some crashes. + OfflineIMAP v6.3.2.1 (2011-03-23) ================================= diff --git a/Makefile b/Makefile index 92ec040..518261f 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -VERSION=6.3.2.2 +VERSION=6.3.2.3 TARGZ=offlineimap_$(VERSION).tar.gz SHELL=/bin/bash RST2HTML=`type rst2html 2>/dev/null 2>&1 && echo rst2html || echo rst2html.py` diff --git a/offlineimap/__init__.py b/offlineimap/__init__.py index 26b6a62..e228282 100644 --- a/offlineimap/__init__.py +++ b/offlineimap/__init__.py @@ -1,7 +1,7 @@ __all__ = ['OfflineImap'] __productname__ = 'OfflineIMAP' -__version__ = "6.3.2.1" +__version__ = "6.3.2.3" __copyright__ = "Copyright (C) 2002 - 2010 John Goerzen" __author__ = "John Goerzen" __author_email__= "john@complete.org" From b840e66d5963edd3af718adb0e99872704d3b8be Mon Sep 17 00:00:00 2001 From: Nicolas Sebrecht Date: Sat, 9 Apr 2016 19:52:33 +0200 Subject: [PATCH 06/10] SQLite: close db when done Backported from 6fb5700f9498fbe85657eaf0624dc1984c202a16. Signed-off-by: Nicolas Sebrecht --- offlineimap/accounts.py | 1 + offlineimap/folder/LocalStatus.py | 3 +++ offlineimap/folder/LocalStatusSQLite.py | 8 +++++++- offlineimap/repository/Base.py | 8 ++++---- offlineimap/repository/LocalStatus.py | 12 +++++++++--- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py index 515c11d..b1e0821 100644 --- a/offlineimap/accounts.py +++ b/offlineimap/accounts.py @@ -595,3 +595,4 @@ def syncfolder(account, remotefolder, quick): for folder in ["statusfolder", "localfolder", "remotefolder"]: if folder in locals(): locals()[folder].dropmessagelistcache() + statusfolder.closefiles() diff --git a/offlineimap/folder/LocalStatus.py b/offlineimap/folder/LocalStatus.py index 78f2134..c627e97 100644 --- a/offlineimap/folder/LocalStatus.py +++ b/offlineimap/folder/LocalStatus.py @@ -155,6 +155,9 @@ class LocalStatusFolder(BaseFolder): self.readstatus(cachefd) cachefd.close() + def closefiles(self): + pass # Closing files is done on a per-transaction basis. + def save(self): """Save changed data to disk. For this backend it is the same as saveall.""" diff --git a/offlineimap/folder/LocalStatusSQLite.py b/offlineimap/folder/LocalStatusSQLite.py index 64adcd1..07cff89 100644 --- a/offlineimap/folder/LocalStatusSQLite.py +++ b/offlineimap/folder/LocalStatusSQLite.py @@ -222,6 +222,12 @@ class LocalStatusSQLiteFolder(BaseFolder): self.messagelist[uid]['labels'] = labels self.messagelist[uid]['mtime'] = row[2] + def closefiles(self): + try: + self.connection.close() + except: + pass + def dropmessagelistcache(self): self.messagelist = {} @@ -338,7 +344,7 @@ class LocalStatusSQLiteFolder(BaseFolder): def savemessageslabelsbulk(self, labels): """ Saves labels from a dictionary in a single database operation. - + """ data = [(', '.join(sorted(l)), uid) for uid, l in labels.items()] self.__sql_write('UPDATE status SET labels=? WHERE id=?', data, executemany=True) diff --git a/offlineimap/repository/Base.py b/offlineimap/repository/Base.py index 8634628..fe33154 100644 --- a/offlineimap/repository/Base.py +++ b/offlineimap/repository/Base.py @@ -170,7 +170,7 @@ class BaseRepository(CustomConfig.ConfigHelperMixin, object): to infinite folder creation cycles.""" if not self.get_create_folders() and not dst_repo.get_create_folders(): - # quick exit if no folder creation is enabled on either side. + # Quick exit if no folder creation is enabled on either side. return src_repo = self @@ -178,7 +178,7 @@ class BaseRepository(CustomConfig.ConfigHelperMixin, object): dst_folders = dst_repo.getfolders() # Do we need to refresh the folder list afterwards? src_haschanged, dst_haschanged = False, False - # Create hashes with the names, but convert the source folders + # Create hashes with the names, but convert the source folders. # to the dest folder's sep. src_hash = {} for folder in src_folders: @@ -191,13 +191,13 @@ class BaseRepository(CustomConfig.ConfigHelperMixin, object): # Find new folders on src_repo. for src_name_t, src_folder in src_hash.iteritems(): - # Don't create on dst_repo, if it is readonly + # Don't create on dst_repo, if it is readonly. if not dst_repo.get_create_folders(): break if src_folder.sync_this and not src_name_t in dst_folders: try: dst_repo.makefolder(src_name_t) - dst_haschanged = True # Need to refresh list + dst_haschanged = True # Need to refresh list. except OfflineImapError as e: self.ui.error(e, exc_info()[2], "Creating folder %s on repository %s"% diff --git a/offlineimap/repository/LocalStatus.py b/offlineimap/repository/LocalStatus.py index fc34a55..5d33cdc 100644 --- a/offlineimap/repository/LocalStatus.py +++ b/offlineimap/repository/LocalStatus.py @@ -46,6 +46,9 @@ class LocalStatusRepository(BaseRepository): # self._folders is a dict of name:LocalStatusFolders() self._folders = {} + def _instanciatefolder(self, foldername): + return self.LocalStatusFolderClass(foldername, self) # Instanciate. + def setup_backend(self, backend): if backend in self.backends.keys(): self._backend = backend @@ -87,19 +90,22 @@ class LocalStatusRepository(BaseRepository): return # bail out in dry-run mode # Create an empty StatusFolder - folder = self.LocalStatusFolderClass(foldername, self) + folder = self._instanciatefolder(foldername) folder.save() + folder.closefiles() # Invalidate the cache. self.forgetfolders() def getfolder(self, foldername): - """Return the Folder() object for a foldername.""" + """Return the Folder() object for a foldername. + + Caller must call closefiles() on the folder when done.""" if foldername in self._folders: return self._folders[foldername] - folder = self.LocalStatusFolderClass(foldername, self) + folder = self._instanciatefolder(foldername) # If folder is empty, try to import data from an other backend. if folder.isnewfolder(): From 3f70d9ef3759373b336678c2dba25f33247817fc Mon Sep 17 00:00:00 2001 From: Nicolas Sebrecht Date: Thu, 12 May 2016 18:27:14 +0200 Subject: [PATCH 07/10] sqlite: open database when we use it rather than at instantiation time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backported from 8e995a69bfa003ab822b55731429d84b3bc5626f. We currently close the database as soon as possible while we handle the status backend but this is still too early because autorefresh induces looping on this code block. Instead of delaying the closing outside of the loop, it's easier to delay the opening as late as possible (inside the loop). The downside is that the database is opened/closed more than once when autorefresh is enabled. The good news is that this make the code much easier. Fixes regression introduces by 6fb5700. Reported-by: Łukasz Żarnowiecki Signed-off-by: Nicolas Sebrecht --- offlineimap/accounts.py | 1 + offlineimap/folder/LocalStatus.py | 3 +++ offlineimap/folder/LocalStatusSQLite.py | 8 +++++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py index b1e0821..e00db6b 100644 --- a/offlineimap/accounts.py +++ b/offlineimap/accounts.py @@ -510,6 +510,7 @@ def syncfolder(account, remotefolder, quick): # Load status folder. statusfolder = statusrepos.getfolder(remotefolder.getvisiblename(). replace(remoterepos.getsep(), statusrepos.getsep())) + statusfolder.openfiles() if localfolder.get_uidvalidity() == None: # This is a new folder, so delete the status cache to be diff --git a/offlineimap/folder/LocalStatus.py b/offlineimap/folder/LocalStatus.py index c627e97..55109a7 100644 --- a/offlineimap/folder/LocalStatus.py +++ b/offlineimap/folder/LocalStatus.py @@ -155,6 +155,9 @@ class LocalStatusFolder(BaseFolder): self.readstatus(cachefd) cachefd.close() + def openfiles(self): + pass # Closing files is done on a per-transaction basis. + def closefiles(self): pass # Closing files is done on a per-transaction basis. diff --git a/offlineimap/folder/LocalStatusSQLite.py b/offlineimap/folder/LocalStatusSQLite.py index 07cff89..79c6e99 100644 --- a/offlineimap/folder/LocalStatusSQLite.py +++ b/offlineimap/folder/LocalStatusSQLite.py @@ -59,9 +59,11 @@ class LocalStatusSQLiteFolder(BaseFolder): raise UserWarning("SQLite database path '%s' is not a directory."% dirname) - # dblock protects against concurrent writes in same connection. + # This lock protects against concurrent writes in same connection. self._dblock = Lock() + self.connection = None + def openfiles(self): # Try to establish connection, no need for threadsafety in __init__. try: self.connection = sqlite.connect(self.filename, check_same_thread=False) @@ -85,10 +87,10 @@ class LocalStatusSQLiteFolder(BaseFolder): cursor = self.connection.execute( "SELECT value from metadata WHERE key='db_version'") except sqlite.DatabaseError: - #db file missing or corrupt, recreate it. + # db file missing or corrupt, recreate it. self.__create_db() else: - # fetch db version and upgrade if needed + # Fetch db version and upgrade if needed. version = int(cursor.fetchone()[0]) if version < LocalStatusSQLiteFolder.cur_version: self.__upgrade_db(version) From 8cca78b265a0bbbb5a6972fcb3d881c97b09e42e Mon Sep 17 00:00:00 2001 From: Nicolas Sebrecht Date: Wed, 8 Jun 2016 02:02:32 +0200 Subject: [PATCH 08/10] v6.7.0.1 Signed-off-by: Nicolas Sebrecht --- Changelog.maint.md | 7 +++++++ offlineimap/__init__.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Changelog.maint.md b/Changelog.maint.md index 344987c..452c3fa 100644 --- a/Changelog.maint.md +++ b/Changelog.maint.md @@ -15,6 +15,13 @@ This is the Changelog of the maintenance branch. If anyone volunteers to maintain it and backport patches, let us know! +### OfflineIMAP v6.7.0.1 (2016-06-08) + +#### Bug Fixes + +* Correctly open and close sqlite databases. + + ### OfflineIMAP v6.3.2.1 (2011-03-23) #### Bug Fixes diff --git a/offlineimap/__init__.py b/offlineimap/__init__.py index 369a477..b0a25bb 100644 --- a/offlineimap/__init__.py +++ b/offlineimap/__init__.py @@ -2,7 +2,7 @@ __all__ = ['OfflineImap'] __productname__ = 'OfflineIMAP' # Expecting trailing "-rcN" or "" for stable releases. -__version__ = "6.7.0" +__version__ = "6.7.0.1" __copyright__ = "Copyright 2002-2016 John Goerzen & contributors" __author__ = "John Goerzen" __author_email__= "offlineimap-project@lists.alioth.debian.org" From 677afb8d8f4b3d41e3842150131a5638fc60c805 Mon Sep 17 00:00:00 2001 From: Nicolas Sebrecht Date: Fri, 22 Jul 2016 00:08:45 +0200 Subject: [PATCH 09/10] sqlite: close the database when no more threads need access It's required to have more than one connection to the database for the maxconnections configuration option to work with threads. However, connection.close() is closing all the connections. Only close the connection when no more thread need it. Backported-from: 856b74407bd7f634cae5a8c2d9b84e13d14c12d2 Github-fix: https://github.com/OfflineIMAP/offlineimap/issues/350 Signed-off-by: Nicolas Sebrecht --- offlineimap/folder/LocalStatusSQLite.py | 81 ++++++++++++++----------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/offlineimap/folder/LocalStatusSQLite.py b/offlineimap/folder/LocalStatusSQLite.py index 79c6e99..8d3e9ee 100644 --- a/offlineimap/folder/LocalStatusSQLite.py +++ b/offlineimap/folder/LocalStatusSQLite.py @@ -42,6 +42,9 @@ class LocalStatusSQLiteFolder(BaseFolder): # Current version of our db format. cur_version = 2 + # Keep track on how many threads need access to the database. + threads_open_count = 0 + threads_open_lock = Lock() def __init__(self, name, repository): self.sep = '.' # Needs to be set before super.__init__() @@ -64,36 +67,39 @@ class LocalStatusSQLiteFolder(BaseFolder): self.connection = None def openfiles(self): - # Try to establish connection, no need for threadsafety in __init__. - try: - self.connection = sqlite.connect(self.filename, check_same_thread=False) - except NameError: - # sqlite import had failed. - raise UserWarning("SQLite backend chosen, but cannot connect " - "with available bindings to '%s'. Is the sqlite3 package " - "installed?."% self.filename), None, exc_info()[2] - except sqlite.OperationalError as e: - # Operation had failed. - raise UserWarning("cannot open database file '%s': %s.\nYou might " - "want to check the rights to that file and if it cleanly opens " - "with the 'sqlite<3>' command."% - (self.filename, e)), None, exc_info()[2] + # Protect the creation/upgrade of database accross threads. + with LocalStatusSQLiteFolder.threads_open_lock: + # Try to establish connection, no need for threadsafety in __init__. + try: + self.connection = sqlite.connect(self.filename, check_same_thread=False) + LocalStatusSQLiteFolder.threads_open_count += 1 + except NameError: + # sqlite import had failed. + raise UserWarning("SQLite backend chosen, but cannot connect " + "with available bindings to '%s'. Is the sqlite3 package " + "installed?."% self.filename), None, exc_info()[2] + except sqlite.OperationalError as e: + # Operation had failed. + raise UserWarning("cannot open database file '%s': %s.\nYou might " + "want to check the rights to that file and if it cleanly opens " + "with the 'sqlite<3>' command."% + (self.filename, e)), None, exc_info()[2] - # Make sure sqlite is in multithreading SERIALIZE mode. - assert sqlite.threadsafety == 1, 'Your sqlite is not multithreading safe.' + # Make sure sqlite is in multithreading SERIALIZE mode. + assert sqlite.threadsafety == 1, 'Your sqlite is not multithreading safe.' - # Test if db version is current enough and if db is readable. - try: - cursor = self.connection.execute( - "SELECT value from metadata WHERE key='db_version'") - except sqlite.DatabaseError: - # db file missing or corrupt, recreate it. - self.__create_db() - else: - # Fetch db version and upgrade if needed. - version = int(cursor.fetchone()[0]) - if version < LocalStatusSQLiteFolder.cur_version: - self.__upgrade_db(version) + # Test if db version is current enough and if db is readable. + try: + cursor = self.connection.execute( + "SELECT value from metadata WHERE key='db_version'") + except sqlite.DatabaseError: + # db file missing or corrupt, recreate it. + self.__create_db() + else: + # Fetch db version and upgrade if needed. + version = int(cursor.fetchone()[0]) + if version < LocalStatusSQLiteFolder.cur_version: + self.__upgrade_db(version) def storesmessages(self): @@ -155,8 +161,8 @@ class LocalStatusSQLiteFolder(BaseFolder): def __upgrade_db(self, from_ver): """Upgrade the sqlite format from version 'from_ver' to current""" - if hasattr(self, 'connection'): - self.connection.close() #close old connections first + if self.connection is not None: + self.connection.close() # Close old connections first. self.connection = sqlite.connect(self.filename, check_same_thread = False) @@ -181,8 +187,8 @@ class LocalStatusSQLiteFolder(BaseFolder): self.connection must point to the opened and valid SQlite database connection.""" - self.ui._msg('Creating new Local Status db for %s:%s' \ - % (self.repository, self)) + self.ui._msg('Creating new Local Status db for %s:%s'% + (self.repository, self)) self.connection.executescript(""" CREATE TABLE metadata (key VARCHAR(50) PRIMARY KEY, value VARCHAR(128)); INSERT INTO metadata VALUES('db_version', '2'); @@ -225,10 +231,13 @@ class LocalStatusSQLiteFolder(BaseFolder): self.messagelist[uid]['mtime'] = row[2] def closefiles(self): - try: - self.connection.close() - except: - pass + with LocalStatusSQLiteFolder.threads_open_lock: + LocalStatusSQLiteFolder.threads_open_count -= 1 + if self.threads_open_count < 1: + try: + self.connection.close() + except: + pass def dropmessagelistcache(self): self.messagelist = {} From 41fdd4ee78ecc22508a364c6768ee75c0a7c6e57 Mon Sep 17 00:00:00 2001 From: Nicolas Sebrecht Date: Fri, 22 Jul 2016 16:34:04 +0200 Subject: [PATCH 10/10] v6.7.0.2 Signed-off-by: Nicolas Sebrecht --- Changelog.maint.md | 7 +++++++ offlineimap/__init__.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Changelog.maint.md b/Changelog.maint.md index 452c3fa..f57b66e 100644 --- a/Changelog.maint.md +++ b/Changelog.maint.md @@ -15,6 +15,13 @@ This is the Changelog of the maintenance branch. If anyone volunteers to maintain it and backport patches, let us know! +### OfflineIMAP v6.7.0.2 (2016-07-22) + +#### Bug Fixes + +* sqlite: close the database when no more threads need connection. + + ### OfflineIMAP v6.7.0.1 (2016-06-08) #### Bug Fixes diff --git a/offlineimap/__init__.py b/offlineimap/__init__.py index b0a25bb..f256564 100644 --- a/offlineimap/__init__.py +++ b/offlineimap/__init__.py @@ -2,7 +2,7 @@ __all__ = ['OfflineImap'] __productname__ = 'OfflineIMAP' # Expecting trailing "-rcN" or "" for stable releases. -__version__ = "6.7.0.1" +__version__ = "6.7.0.2" __copyright__ = "Copyright 2002-2016 John Goerzen & contributors" __author__ = "John Goerzen" __author_email__= "offlineimap-project@lists.alioth.debian.org"