From f58ec21ec820d417afc705c73b4c019b94cfbf2f Mon Sep 17 00:00:00 2001 From: Michiel Nauta Date: Sun, 20 Nov 2011 09:34:30 +0000 Subject: [PATCH] 5368: Loading familytree with downgraded Berkeley db should generate expressive error svn: r18468 --- src/gen/db/dbconst.py | 4 +++- src/gen/db/exceptions.py | 24 ++++++++++++++++++++++++ src/gen/db/write.py | 39 ++++++++++++++++++++++++++++++++++++--- src/gui/dbloader.py | 3 +++ 4 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/gen/db/dbconst.py b/src/gen/db/dbconst.py index a62f8553b..04293b5db 100644 --- a/src/gen/db/dbconst.py +++ b/src/gen/db/dbconst.py @@ -38,7 +38,8 @@ Declare constants used by database modules __all__ = ( ('DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO', 'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN', - 'DBRECOVFN', 'DBLOGNAME', 'DBFLAGS_O', 'DBFLAGS_R', 'DBFLAGS_D', + 'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'DBFLAGS_O', 'DBFLAGS_R', + 'DBFLAGS_D', ) + ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'EVENT_KEY', @@ -53,6 +54,7 @@ DBEXT = ".db" # File extension to be used for database files DBUNDOFN = "undo.db" # File name of 'undo' database DBLOCKFN = "lock" # File name of lock file DBRECOVFN = "need_recover" # File name of recovery file +BDBVERSFN = "bdbversion.txt"# File name of Berkeley DB version file DBLOGNAME = ".Db" # Name of logger DBMODE_R = "r" # Read-only access DBMODE_W = "w" # Full Reaw/Write access diff --git a/src/gen/db/exceptions.py b/src/gen/db/exceptions.py index f066d47c9..1e0b47398 100644 --- a/src/gen/db/exceptions.py +++ b/src/gen/db/exceptions.py @@ -79,6 +79,30 @@ class DbVersionError(Exception): "Gramps.\nPlease upgrade to the corresponding version or use " "XML for porting data between different database versions.") +class BsddbDowngradeError(Exception): + """ + Error used to report that the Berkeley database used to create the family + tree is of a version that is too new to be supported by the current version. + """ + def __init__(self, env_version, bdb_version): + Exception.__init__(self) + self.env_version = str(env_version) + self.bdb_version = str(bdb_version) + + def __str__(self): + return _('Gramps stores its data in a Berkeley Database. ' + 'The family tree you try to load was created with version ' + '%(env_version)s of the Berkeley DB. However, the Gramps ' + 'version in use right now employs version %(bdb_version)s ' + 'of the Berkeley DB. So you are trying to load data created ' + 'in a newer format into an older program; this is bound to ' + 'fail. The right approach in this case is to use XML export ' + 'and import. So try to open the family tree on that computer ' + 'with that software that created the family tree, export it ' + 'to XML and load that XML into the version of Gramps you ' + 'intend to use.') % {'env_version': self.env_version, + 'bdb_version': self.bdb_version} + class DbEnvironmentError(Exception): """ Error used to report that the database 'environment' could not be opened. diff --git a/src/gen/db/write.py b/src/gen/db/write.py index 213293952..458664655 100644 --- a/src/gen/db/write.py +++ b/src/gen/db/write.py @@ -56,9 +56,9 @@ else: from gen.lib import (GenderStats, Person, Family, Event, Place, Source, MediaObject, Repository, Note, Tag) from gen.db import (DbBsddbRead, DbWriteBase, BSDDBTxn, - DbTxn, BsddbBaseCursor, DbVersionError, DbEnvironmentError, - DbUpgradeRequiredError, find_surname, find_surname_name, - DbUndoBSDDB as DbUndo) + DbTxn, BsddbBaseCursor, BsddbDowngradeError, DbVersionError, + DbEnvironmentError, DbUpgradeRequiredError, find_surname, + find_surname_name, DbUndoBSDDB as DbUndo) from gen.db.dbconst import * from gen.utils.callback import Callback from gen.updatecallback import UpdateCallback @@ -224,6 +224,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.secondary_connected = False self.has_changed = False self.brief_name = None + self.update_env_version = False def catch_db_error(func): """ @@ -349,6 +350,27 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): with BSDDBTxn(self.env, self.metadata) as txn: txn.put('mediapath', path) + def __check_bdb_version(self, name): + """Older version of Berkeley DB can't read data created by a newer + version.""" + bdb_version = db.version() + env_version = (0, 0, 0) + versionpath = os.path.join(self.path, BDBVERSFN) + try: + with open(versionpath, "r") as version_file: + env_version = version_file.read().strip() + env_version = tuple(map(int, env_version[1:-1].split(', '))) + except: + # Just assume that the Berkeley DB version is OK. + pass + if (env_version[0] > bdb_version[0]) or \ + (env_version[0] == bdb_version[0] and + env_version[1] > bdb_version[1]): + clear_lock_file(name) + raise BsddbDowngradeError(env_version, bdb_version) + elif env_version != bdb_version and not self.readonly: + self.update_env_version = True + @catch_db_error def version_supported(self): dbversion = self.metadata.get('version', default=0) @@ -400,6 +422,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.path = self.full_name self.brief_name = os.path.basename(name) + self.__check_bdb_version(name) + # Set up database environment self.env = db.DBEnv() self.env.set_cachesize(0, DBCACHE) @@ -1099,6 +1123,15 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.undo_history_callback = None self.undodb = None + if self.update_env_version: + versionpath = os.path.join(self.path, BDBVERSFN) + try: + with open(versionpath, "w") as version_file: + version_file.write(str(db.version())) + except: + # Storing the version of Berkeley Db is not really vital. + pass + try: clear_lock_file(self.get_save_path()) except IOError: diff --git a/src/gui/dbloader.py b/src/gui/dbloader.py index 296413a8e..5af47031d 100644 --- a/src/gui/dbloader.py +++ b/src/gui/dbloader.py @@ -309,6 +309,9 @@ class DbLoader(CLIDbLoader): self.dbstate.db.set_save_path(filename) else: self.dbstate.no_database() + except gen.db.exceptions.BsddbDowngradeError, msg: + self.dbstate.no_database() + self._errordialog( _("Cannot open database"), str(msg)) except gen.db.exceptions.DbVersionError, msg: self.dbstate.no_database() self._errordialog( _("Cannot open database"), str(msg))