From 735adfb0c181b54888f0b910ece977163323555c Mon Sep 17 00:00:00 2001 From: Tim G L Lyons Date: Thu, 9 May 2013 17:24:40 +0000 Subject: [PATCH] 0006529: Cancelling database upgrade can corrupt the database. Ensure database is unlocked when cancelling upgrade. Make links in dialogues into clickable hyper-links. Ensure dialogue windows stays on top. Reword warning and error messages to use Bsddb version, schema version and Family Tree consistently, to be clearer about the choice the user is being offered, and to provide hyper-links to more information. svn: r22219 --- gramps/cli/grampscli.py | 3 + gramps/gen/db/exceptions.py | 119 +++++++++++++++++++++++------------- gramps/gen/db/write.py | 87 +++++++++++++++++--------- gramps/gui/dbloader.py | 39 +++++++++--- gramps/gui/dialog.py | 13 ++++ 5 files changed, 183 insertions(+), 78 deletions(-) diff --git a/gramps/cli/grampscli.py b/gramps/cli/grampscli.py index 562f031d3..0d7c858c5 100644 --- a/gramps/cli/grampscli.py +++ b/gramps/cli/grampscli.py @@ -163,6 +163,9 @@ class CLIDbLoader(object): self._errordialog( _("Cannot open database"), str(msg)) except gen.db.exceptions.BsddbUpgradeRequiredError as msg: self.dbstate.no_database() + except gen.db.exceptions.BsddbDowngradeRequiredError, msg: + self.dbstate.no_database() + self._errordialog( _("Cannot open database"), str(msg)) self._errordialog( _("Cannot open database"), str(msg)) except gen.db.exceptions.BsddbDowngradeError as msg: self.dbstate.no_database() diff --git a/gramps/gen/db/exceptions.py b/gramps/gen/db/exceptions.py index 22c0bc87b..cdbc96d01 100644 --- a/gramps/gen/db/exceptions.py +++ b/gramps/gen/db/exceptions.py @@ -79,13 +79,13 @@ class DbVersionError(Exception): self.max_vers = max_vers def __str__(self): - return _("The schema version is not supported by this version of " - "Gramps.\n\n" - "This Family Tree is schema version %(tree_vers)s, and this " - "version of Gramps supports versions %(min_vers)s to " - "%(max_vers)s\n\n" - "Please upgrade to the corresponding version or use " - "XML for porting data between different database versions.") %\ + return _('The schema version is not supported by this version of ' + 'Gramps.\n\n' + 'This Family Tree is schema version %(tree_vers)s, and this ' + 'version of Gramps supports versions %(min_vers)s to ' + '%(max_vers)s\n\n' + 'Please upgrade to the corresponding version or use ' + 'XML for porting data between different schema versions.') %\ {'tree_vers': self.tree_vers, 'min_vers': self.min_vers, 'max_vers': self.max_vers} @@ -101,17 +101,42 @@ class BsddbDowngradeError(Exception): 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, + return _('The Family Tree you are trying to load is in the Bsddb ' + 'version %(env_version)s format. This version of Gramps uses ' + 'Bsddb version %(bdb_version)s. So you are trying to load ' + 'data created in a newer format into an older program, and ' + 'this is bound to fail.\n\n' + 'You should start your newer version of Gramps and ' + '' + 'make a backup of your Family Tree. You can then import ' + 'this backup into this version of Gramps.') % \ + {'env_version': self.env_version, + 'bdb_version': self.bdb_version} + +class BsddbDowngradeRequiredError(Exception): + """ + Error used to report that the Berkeley database used to create the family + tree is of a version that is newer than the current version, but it may be + possible to open the tree, because the difference is only a point upgrade + (i.e. a difference in the last digit of the version tuple). + """ + 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 _('The Family Tree you are trying to load is in the Bsddb ' + 'version %(env_version)s format. This version of Gramps uses ' + 'Bsddb version %(bdb_version)s. So you are trying to load ' + 'data created in a newer format into an older program. In ' + 'this particular case, the difference is very small, so it ' + 'may work.\n\n' + 'If you have not already made a backup of your Family Tree, ' + 'then you should start your newer version of Gramps and ' + '' + 'make a backup of your Family Tree.') % \ + {'env_version': self.env_version, 'bdb_version': self.bdb_version} class BsddbUpgradeRequiredError(Exception): @@ -125,18 +150,20 @@ class BsddbUpgradeRequiredError(Exception): self.bsddb_version = str(bsddb_version) def __str__(self): - return _('The BSDDB version of the Family Tree you are trying to open ' - 'needs to be upgraded from %(env_version)s to %(bdb_version)s.\n\n' - 'This probably means that the Family Tree was created with ' - 'an old version of Gramps. Opening the tree with this version ' - 'of Gramps may irretrievably corrupt your tree. You are ' - 'strongly advised to backup your tree before proceeding, ' - 'see: \n' - 'http://www.gramps-project.org/wiki/index.php?title=How_to_make_a_backup\n\n' - 'If you have made a backup, then you can get Gramps to try ' - 'to open the tree and upgrade it') % \ - {'env_version': self.env_version, - 'bdb_version': self.bsddb_version} + return _('The Family Tree you are trying to load is in the Bsddb ' + 'version %(env_version)s format. This version of Gramps uses ' + 'Bsddb version %(bdb_version)s. Therefore you cannot load ' + 'this Family Tree without upgrading the Bsddb version of the ' + 'Family Tree.\n\n' + 'Opening the Family Tree with this version of Gramps might ' + 'irretrievably corrupt your Family Tree. You are strongly ' + 'advised to backup your Family Tree.\n\n' + 'If you have not already made a backup of your Family Tree, ' + 'then you should start your old version of Gramps and ' + '' + 'make a backup of your Family Tree.') % \ + {'env_version': self.env_version, + 'bdb_version': self.bsddb_version} class DbEnvironmentError(Exception): """ @@ -167,17 +194,27 @@ class DbUpgradeRequiredError(Exception): Error used to report that a database needs to be upgraded before it can be used. """ - def __init__(self): + def __init__(self, oldschema, newschema): Exception.__init__(self) + self.oldschema = oldschema + self.newschema = newschema def __str__(self): - return _("The Family Tree structure has changed since the version of" - " Gramps you used to create this tree.\n\n" - "Therefore, you cannot open this Family Tree without upgrading the" - " definition of the structure.\n" - "If you upgrade then you won't be able to use previous " - "versions of Gramps, also not with the .gramps xml export!\n\n" - "Upgrading is a difficult task that may not be interrupted, or " - "Gramps will irretrievably corrupt your tree. Therefore, create " - "a backup copy first. See: \n" - "http://www.gramps-project.org/wiki/index.php?title=How_to_make_a_backup") + return _('The Family Tree you are trying to load is in the schema ' + 'version %(oldschema)s format. This version of Gramps uses ' + 'schema version %(newschema)s. Therefore you cannot load this ' + 'Family Tree without upgrading the schema version of the ' + 'Family Tree.\n\n' + 'If you upgrade then you won\'t be able to use the previous ' + 'version of Gramps, even if you subsequently ' + 'backup ' + 'or export ' + 'your upgraded Family Tree.\n\n' + 'Upgrading is a difficult task which could irretrievably ' + 'corrupt your Family Tree if it is interrupted or fails.\n\n' + 'If you have not already made a backup of your Family Tree, ' + 'then you should start your old version of Gramps and ' + 'make a backup ' + 'of your Family Tree.') % \ + {'oldschema': self.oldschema, + 'newschema': self.newschema} diff --git a/gramps/gen/db/write.py b/gramps/gen/db/write.py index bcbf2fb5f..54954d996 100644 --- a/gramps/gen/db/write.py +++ b/gramps/gen/db/write.py @@ -407,7 +407,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): return (grampsdb_path, db_code) = os.path.split(dirname) dotgramps_path = os.path.dirname(grampsdb_path) - zipname = title + time.strftime("%Y-%m-%d %H-%M-%S") + ".zip" + zipname = title + time.strftime("_%Y-%m-%d_%H-%M-%S") + ".zip" if sys.version_info[0] < 3: zipname = zipname.encode(glocale.getfilesystemencoding()) zippath = os.path.join(dotgramps_path, zipname) @@ -420,7 +420,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): "delete the zip file at %s" % zippath) - def __check_bdb_version(self, name, force_bsddb_upgrade=False): + def __check_bdb_version(self, name, force_bsddb_upgrade=False, + force_bsddb_downgrade=False): """Older version of Berkeley DB can't read data created by a newer version.""" bdb_version = db.version() @@ -434,31 +435,58 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): with open(versionpath, "r") as version_file: bsddb_version = version_file.read().strip() env_version = tuple(map(int, bsddb_version[1:-1].split(', '))) - 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: - return else: # bsddb version is unknown - bsddb_version = "Unknown" + env_version = "Unknown" - # An upgrade is needed, raise an exception unless the user has allowed - # an upgrade - if not force_bsddb_upgrade: - _LOG.debug("Bsddb upgrade required from %s to %s" % - (bsddb_version, str(bdb_version))) - raise exceptions.BsddbUpgradeRequiredError(bsddb_version, - str(bdb_version)) - - if not self.readonly: - _LOG.warning("Bsddb upgrade requested from %s to %s" % - (bsddb_version, str(bdb_version))) - self.update_env_version = True - # Make a backup of the database files anyway - self.__make_zip_backup(name) + if env_version == "Unknown" or \ + (env_version[0] < bdb_version[0]) or \ + (env_version[0] == bdb_version[0] and + env_version[1] < bdb_version[1]) or \ + (env_version[0] == bdb_version[0] and + env_version[1] == bdb_version[1] and + env_version[2] < bdb_version[2]): + # an upgrade is needed + if not force_bsddb_upgrade: + _LOG.debug("Bsddb upgrade required from %s to %s" % + (bsddb_version, str(bdb_version))) + clear_lock_file(name) + raise exceptions.BsddbUpgradeRequiredError(bsddb_version, + str(bdb_version)) + if not self.readonly: + _LOG.warning("Bsddb upgrade requested from %s to %s" % + (bsddb_version, str(bdb_version))) + self.update_env_version = True + # Make a backup of the database files anyway + self.__make_zip_backup(name) + elif (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[0] == bdb_version[0] and + env_version[1] == bdb_version[1] and + env_version[2] > bdb_version[2]): + # A down-grade may be possible + if not force_bsddb_downgrade: + _LOG.debug("Bsddb downgrade required from %s to %s" % + (bsddb_version, str(bdb_version))) + clear_lock_file(name) + raise exceptions.BsddbDowngradeRequiredError(bsddb_version, + str(bdb_version)) + # Try to do a down-grade + if not self.readonly: + _LOG.warning("Bsddb downgrade requested from %s to %s" % + (bsddb_version, str(bdb_version))) + self.update_env_version = True + # Make a backup of the database files anyway + self.__make_zip_backup(name) + elif env_version == bdb_version: + # Bsddb version is OK + pass + else: + # This can't happen + raise "Comparison between Bsddb version failed" @catch_db_error def version_supported(self): @@ -493,7 +521,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): @catch_db_error def load(self, name, callback, mode=DBMODE_W, force_schema_upgrade=False, - force_bsddb_upgrade=False): + force_bsddb_upgrade=False, force_bsddb_downgrade=False): if self.__check_readonly(name): mode = DBMODE_R @@ -513,7 +541,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): self.path = self.full_name self.brief_name = os.path.basename(name) - self.__check_bdb_version(name, force_bsddb_upgrade) + self.__check_bdb_version(name, force_bsddb_upgrade, + force_bsddb_downgrade) # Set up database environment self.env = db.DBEnv() @@ -632,14 +661,16 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): # or rebuilt by upgrade as well. In any case, the # self.secondary_connected flag should be set accordingly. if self.need_schema_upgrade(): + oldschema = self.metadata.get(b'version', default=0) + newschema = _DBVERSION _LOG.debug("Schema upgrade required from %s to %s" % - (self.metadata.get(b'version', default=0), _DBVERSION)) + (oldschema, newschema)) if force_schema_upgrade == True: self.gramps_upgrade(callback) else: self.__close_early() clear_lock_file(name) - raise DbUpgradeRequiredError() + raise DbUpgradeRequiredError(oldschema, newschema) if callback: callback(50) diff --git a/gramps/gui/dbloader.py b/gramps/gui/dbloader.py index d8eaf2154..ce596820a 100644 --- a/gramps/gui/dbloader.py +++ b/gramps/gui/dbloader.py @@ -62,7 +62,8 @@ from gramps.gen.db.exceptions import (DbUpgradeRequiredError, BsddbDowngradeError, DbVersionError, DbEnvironmentError, - BsddbUpgradeRequiredError) + BsddbUpgradeRequiredError, + BsddbDowngradeRequiredError) from gramps.gen.constfunc import STRTYPE from gramps.gen.utils.file import get_unicode_path_from_file_chooser from .pluginmanager import GuiPluginManager @@ -307,40 +308,60 @@ class DbLoader(CLIDbLoader): force_schema_upgrade = False force_bsddb_upgrade = False + force_bsddb_downgrade = False try: while True: try: db.load(filename, self._pulse_progress, mode, force_schema_upgrade, - force_bsddb_upgrade) + force_bsddb_upgrade, + force_bsddb_downgrade) db.set_save_path(filename) self.dbstate.change_database(db) break except DbUpgradeRequiredError as msg: - if QuestionDialog2(_("Need to upgrade database!"), + if QuestionDialog2(_("Are you sure you want to upgrade " + "this Family Tree?"), str(msg), - _("Upgrade now"), - _("Cancel")).run(): + _("I have made a backup,\n" + "please upgrade my Family Tree"), + _("Cancel"), self.uistate.window).run(): force_schema_upgrade = True force_bsddb_upgrade = False + force_bsddb_downgrade = False else: self.dbstate.no_database() break except BsddbUpgradeRequiredError as msg: - if QuestionDialog2(_("Need to upgrade BSDDB database!"), + if QuestionDialog2(_("Are you sure you want to upgrade " + "this Family Tree?"), str(msg), - _("I have made a backup, " + _("I have made a backup,\n" "please upgrade my tree"), - _("Cancel")).run(): + _("Cancel"), self.uistate.window).run(): force_schema_upgrade = False force_bsddb_upgrade = True + force_bsddb_downgrade = False + else: + self.dbstate.no_database() + break + except BsddbDowngradeRequiredError as msg: + if QuestionDialog2(_("Are you sure you want to downgrade " + "this Family Tree?"), + str(msg), + _("I have made a backup,\n" + "please downgrade my Family Tree"), + _("Cancel"), self.uistate.window).run(): + force_schema_upgrade = False + force_bsddb_upgrade = False + force_bsddb_downgrade = True else: self.dbstate.no_database() break # Get here is there is an exception the while loop does not handle except BsddbDowngradeError as msg: self.dbstate.no_database() - self._errordialog( _("Cannot open database"), str(msg)) + self._warn( _("Cannot open database"), str(msg)) except DbVersionError as msg: self.dbstate.no_database() self._errordialog( _("Cannot open database"), str(msg)) diff --git a/gramps/gui/dialog.py b/gramps/gui/dialog.py index f3555a572..11dd6aa31 100644 --- a/gramps/gui/dialog.py +++ b/gramps/gui/dialog.py @@ -112,6 +112,12 @@ class QuestionDialog(object): if response == Gtk.ResponseType.ACCEPT: task() +from display import display_url +def on_activate_link(label, uri): + # see aboutdialog.py _show_url() + display_url(uri) + return True + class QuestionDialog2(object): def __init__(self, msg1, msg2, label_msg1, label_msg2, parent=None): self.xml = Glade(toplevel='questiondialog') @@ -125,6 +131,8 @@ class QuestionDialog2(object): label1.set_use_markup(True) label2 = self.xml.get_object('qd_label2') + # see https://github.com/emesene/emesene/issues/723 + label2.connect('activate-link', on_activate_link) label2.set_text(msg2) label2.set_use_markup(True) @@ -228,6 +236,11 @@ class WarningDialog(Gtk.MessageDialog): buttons=Gtk.ButtonsType.CLOSE) self.set_markup('%s' % msg1) self.format_secondary_markup(msg2) + # FIXME: Hyper-links in the secondary text display as underlined text, + # but clicking on the link fails with + # GtkWarning: Unable to show 'http://www.gramps-project.org/wiki/index.php?title=How_to_make_a_backup': Operation not supported + # self.connect('activate-link'... fails with + # : unknown signal name: activate-link self.set_icon(ICON) self.set_title("%s - Gramps" % msg1) self.show()