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 e66091d25..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 217677d19..7eb43e9d8 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 92d27b9f0..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()