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: r22217
This commit is contained in:
Tim G L Lyons
2013-05-09 17:23:37 +00:00
parent 51bedcd46c
commit 109e64f657
5 changed files with 182 additions and 72 deletions

View File

@@ -107,6 +107,12 @@ class QuestionDialog(object):
if response == gtk.RESPONSE_ACCEPT: if response == gtk.RESPONSE_ACCEPT:
task() task()
from GrampsDisplay import url as display_url
def on_activate_link(label, uri):
# see aboutdialog.py _show_url()
display_url(uri)
return True
class QuestionDialog2(object): class QuestionDialog2(object):
def __init__(self, msg1, msg2, label_msg1, label_msg2, parent=None): def __init__(self, msg1, msg2, label_msg1, label_msg2, parent=None):
self.xml = Glade(toplevel='questiondialog') self.xml = Glade(toplevel='questiondialog')
@@ -120,6 +126,8 @@ class QuestionDialog2(object):
label1.set_use_markup(True) label1.set_use_markup(True)
label2 = self.xml.get_object('qd_label2') 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_text(msg2)
label2.set_use_markup(True) label2.set_use_markup(True)
@@ -217,6 +225,11 @@ class WarningDialog(gtk.MessageDialog):
buttons=gtk.BUTTONS_CLOSE) buttons=gtk.BUTTONS_CLOSE)
self.set_markup('<span weight="bold" size="larger">%s</span>' % msg1) self.set_markup('<span weight="bold" size="larger">%s</span>' % msg1)
self.format_secondary_markup(msg2) 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
# <WarningDialog object at 0x4880300 (GtkMessageDialog at 0x5686010)>: unknown signal name: activate-link
self.set_icon(ICON) self.set_icon(ICON)
self.set_title("%s - Gramps" % msg1) self.set_title("%s - Gramps" % msg1)
self.show() self.show()

View File

@@ -163,6 +163,9 @@ class CLIDbLoader(object):
except gen.db.exceptions.BsddbDowngradeError, msg: except gen.db.exceptions.BsddbDowngradeError, msg:
self.dbstate.no_database() self.dbstate.no_database()
self._errordialog( _("Cannot open database"), str(msg)) self._errordialog( _("Cannot open database"), str(msg))
except gen.db.exceptions.BsddbDowngradeRequiredError, msg:
self.dbstate.no_database()
self._errordialog( _("Cannot open database"), str(msg))
except gen.db.exceptions.DbUpgradeRequiredError, msg: except gen.db.exceptions.DbUpgradeRequiredError, msg:
self.dbstate.no_database() self.dbstate.no_database()
self._errordialog( _("Cannot open database"), str(msg)) self._errordialog( _("Cannot open database"), str(msg))

View File

@@ -78,13 +78,13 @@ class DbVersionError(Exception):
self.max_vers = max_vers self.max_vers = max_vers
def __str__(self): def __str__(self):
return _("The schema version is not supported by this version of " return _('The schema version is not supported by this version of '
"Gramps.\n\n" 'Gramps.\n\n'
"This Family tree is schema version %(tree_vers)s, and this " 'This Family Tree is schema version %(tree_vers)s, and this '
"version of Gramps supports versions %(min_vers)s to " 'version of Gramps supports versions %(min_vers)s to '
"%(max_vers)s\n\n" '%(max_vers)s\n\n'
"Please upgrade to the corresponding version or use " 'Please upgrade to the corresponding version or use '
"XML for porting data between different database versions.") %\ 'XML for porting data between different schema versions.') %\
{'tree_vers': self.tree_vers, {'tree_vers': self.tree_vers,
'min_vers': self.min_vers, 'min_vers': self.min_vers,
'max_vers': self.max_vers} 'max_vers': self.max_vers}
@@ -100,17 +100,42 @@ class BsddbDowngradeError(Exception):
self.bdb_version = str(bdb_version) self.bdb_version = str(bdb_version)
def __str__(self): def __str__(self):
return _('Gramps stores its data in a Berkeley Database. ' return _('The Family Tree you are trying to load is in the Bsddb '
'The family tree you try to load was created with version ' 'version %(env_version)s format. This version of Gramps uses '
'%(env_version)s of the Berkeley DB. However, the Gramps ' 'Bsddb version %(bdb_version)s. So you are trying to load '
'version in use right now employs version %(bdb_version)s ' 'data created in a newer format into an older program, and '
'of the Berkeley DB. So you are trying to load data created ' 'this is bound to fail.\n\n'
'in a newer format into an older program; this is bound to ' 'You should start your <b>newer</b> version of Gramps and '
'fail. The right approach in this case is to use XML export ' '<a href="http://www.gramps-project.org/wiki/index.php?title=How_to_make_a_backup">'
'and import. So try to open the family tree on that computer ' 'make a backup</a> of your Family Tree. You can then import '
'with that software that created the family tree, export it ' 'this backup into this version of Gramps.') % \
'to XML and load that XML into the version of Gramps you ' {'env_version': self.env_version,
'intend to use.') % {'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 <b>newer</b> version of Gramps and '
'<a href="http://www.gramps-project.org/wiki/index.php?title=How_to_make_a_backup">'
'make a backup</a> of your Family Tree.') % \
{'env_version': self.env_version,
'bdb_version': self.bdb_version} 'bdb_version': self.bdb_version}
class BsddbUpgradeRequiredError(Exception): class BsddbUpgradeRequiredError(Exception):
@@ -124,18 +149,20 @@ class BsddbUpgradeRequiredError(Exception):
self.bsddb_version = str(bsddb_version) self.bsddb_version = str(bsddb_version)
def __str__(self): def __str__(self):
return _('The BSDDB version of the Family Tree you are trying to open ' return _('The Family Tree you are trying to load is in the Bsddb '
'needs to be upgraded from %(env_version)s to %(bdb_version)s.\n\n' 'version %(env_version)s format. This version of Gramps uses '
'This probably means that the Family Tree was created with ' 'Bsddb version %(bdb_version)s. Therefore you cannot load '
'an old version of Gramps. Opening the tree with this version ' 'this Family Tree without upgrading the Bsddb version of the '
'of Gramps may irretrievably corrupt your tree. You are ' 'Family Tree.\n\n'
'strongly advised to backup your tree before proceeding, ' 'Opening the Family Tree with this version of Gramps might '
'see: \n' 'irretrievably corrupt your Family Tree. You are strongly '
'http://www.gramps-project.org/wiki/index.php?title=How_to_make_a_backup\n\n' 'advised to backup your Family Tree.\n\n'
'If you have made a backup, then you can get Gramps to try ' 'If you have not already made a backup of your Family Tree, '
'to open the tree and upgrade it') % \ 'then you should start your <b>old</b> version of Gramps and '
{'env_version': self.env_version, '<a href="http://www.gramps-project.org/wiki/index.php?title=How_to_make_a_backup">'
'bdb_version': self.bsddb_version} 'make a backup</a> of your Family Tree.') % \
{'env_version': self.env_version,
'bdb_version': self.bsddb_version}
class DbEnvironmentError(Exception): class DbEnvironmentError(Exception):
""" """
@@ -166,11 +193,27 @@ class DbUpgradeRequiredError(Exception):
Error used to report that a database needs to be upgraded before it can be Error used to report that a database needs to be upgraded before it can be
used. used.
""" """
def __init__(self): def __init__(self, oldschema, newschema):
Exception.__init__(self) Exception.__init__(self)
self.oldschema = oldschema
self.newschema = newschema
def __str__(self): def __str__(self):
return _("You cannot open this database without upgrading it.\n" return _('The Family Tree you are trying to load is in the schema '
"If you upgrade then you won't be able to use previous " 'version %(oldschema)s format. This version of Gramps uses '
"versions of Gramps.\n" 'schema version %(newschema)s. Therefore you cannot load this '
"You might want to make a backup copy first.") '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 '
'<a href="http://www.gramps-project.org/wiki/index.php?title=Gramps_4.0_Wiki_Manual_-_Manage_Family_Trees#Backing_up_a_Family_Tree">backup</a> '
'or <a href="http://www.gramps-project.org/wiki/index.php?title=Gramps_4.0_Wiki_Manual_-_Manage_Family_Trees#Export_into_Gramps_formats">export</a> '
'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 <b>old</b> version of Gramps and '
'<a href="http://www.gramps-project.org/wiki/index.php?title=How_to_make_a_backup">make a backup</a> '
'of your Family Tree.') % \
{'oldschema': self.oldschema,
'newschema': self.newschema}

View File

@@ -366,7 +366,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
return return
(grampsdb_path, db_code) = os.path.split(dirname) (grampsdb_path, db_code) = os.path.split(dirname)
dotgramps_path = os.path.dirname(grampsdb_path) 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"
zipname = zipname.encode(getfilesystemencoding()) zipname = zipname.encode(getfilesystemencoding())
zippath = os.path.join(dotgramps_path, zipname) zippath = os.path.join(dotgramps_path, zipname)
myzip = zipfile.ZipFile(zippath, 'w') myzip = zipfile.ZipFile(zippath, 'w')
@@ -378,7 +378,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
"delete the zip file at %s" % "delete the zip file at %s" %
zippath) 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 Older version of Berkeley DB can't read data created by a newer
version. version.
@@ -397,31 +398,58 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
with open(versionpath, "r") as version_file: with open(versionpath, "r") as version_file:
bsddb_version = version_file.read().strip() bsddb_version = version_file.read().strip()
env_version = tuple(map(int, bsddb_version[1:-1].split(', '))) 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: else:
# bsddb version is unknown # bsddb version is unknown
bsddb_version = "Unknown" env_version = "Unknown"
# An upgrade is needed, raise an exception unless the user has allowed if env_version == "Unknown" or \
# an upgrade (env_version[0] < bdb_version[0]) or \
if not force_bsddb_upgrade: (env_version[0] == bdb_version[0] and
_LOG.debug("Bsddb upgrade required from %s to %s" % env_version[1] < bdb_version[1]) or \
(bsddb_version, str(bdb_version))) (env_version[0] == bdb_version[0] and
raise exceptions.BsddbUpgradeRequiredError(bsddb_version, env_version[1] == bdb_version[1] and
str(bdb_version)) env_version[2] < bdb_version[2]):
# an upgrade is needed
if not self.readonly: if not force_bsddb_upgrade:
_LOG.warning("Bsddb upgrade requested from %s to %s" % _LOG.debug("Bsddb upgrade required from %s to %s" %
(bsddb_version, str(bdb_version))) (bsddb_version, str(bdb_version)))
self.update_env_version = True clear_lock_file(name)
# Make a backup of the database files anyway raise exceptions.BsddbUpgradeRequiredError(bsddb_version,
self.__make_zip_backup(name) 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 @catch_db_error
def version_supported(self): def version_supported(self):
@@ -456,7 +484,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
@catch_db_error @catch_db_error
def load(self, name, callback, mode=DBMODE_W, force_schema_upgrade=False, 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): if self.__check_readonly(name):
mode = DBMODE_R mode = DBMODE_R
@@ -476,7 +504,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
self.path = self.full_name self.path = self.full_name
self.brief_name = os.path.basename(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 # Set up database environment
self.env = db.DBEnv() self.env = db.DBEnv()
@@ -591,14 +620,16 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
# or rebuilt by upgrade as well. In any case, the # or rebuilt by upgrade as well. In any case, the
# self.secondary_connected flag should be set accordingly. # self.secondary_connected flag should be set accordingly.
if self.need_schema_upgrade(): if self.need_schema_upgrade():
oldschema = self.metadata.get('version', default=0)
newschema = _DBVERSION
_LOG.debug("Schema upgrade required from %s to %s" % _LOG.debug("Schema upgrade required from %s to %s" %
(self.metadata.get('version', default=0), _DBVERSION)) (oldschema, newschema))
if force_schema_upgrade == True: if force_schema_upgrade == True:
self.gramps_upgrade(callback) self.gramps_upgrade(callback)
else: else:
self.__close_early() self.__close_early()
clear_lock_file(name) clear_lock_file(name)
raise DbUpgradeRequiredError() raise DbUpgradeRequiredError(oldschema, newschema)
if callback: if callback:
callback(50) callback(50)

View File

@@ -297,39 +297,59 @@ class DbLoader(CLIDbLoader):
force_schema_upgrade = False force_schema_upgrade = False
force_bsddb_upgrade = False force_bsddb_upgrade = False
force_bsddb_downgrade = False
try: try:
while True: while True:
try: try:
self.dbstate.db.load(filename, self._pulse_progress, self.dbstate.db.load(filename, self._pulse_progress,
mode, force_schema_upgrade, mode, force_schema_upgrade,
force_bsddb_upgrade) force_bsddb_upgrade,
force_bsddb_downgrade)
self.dbstate.db.set_save_path(filename) self.dbstate.db.set_save_path(filename)
break break
except gen.db.exceptions.DbUpgradeRequiredError, msg: except gen.db.exceptions.DbUpgradeRequiredError, msg:
if QuestionDialog2(_("Need to upgrade database!"), if QuestionDialog2(_("Are you sure you want to upgrade "
"this Family Tree?"),
str(msg), str(msg),
_("Upgrade now"), _("I have made a backup,\n"
_("Cancel")).run(): "please upgrade my Family Tree"),
_("Cancel"), self.uistate.window).run():
force_schema_upgrade = True force_schema_upgrade = True
force_bsddb_upgrade = False force_bsddb_upgrade = False
force_bsddb_downgrade = False
else: else:
self.dbstate.no_database() self.dbstate.no_database()
break break
except gen.db.exceptions.BsddbUpgradeRequiredError, msg: except gen.db.exceptions.BsddbUpgradeRequiredError, msg:
if QuestionDialog2(_("Need to upgrade BSDDB database!"), if QuestionDialog2(_("Are you sure you want to upgrade "
"this Family Tree?"),
str(msg), str(msg),
_("I have made a backup, " _("I have made a backup,\n"
"please upgrade my tree"), "please upgrade my Family Tree"),
_("Cancel")).run(): _("Cancel"), self.uistate.window).run():
force_schema_upgrade = False force_schema_upgrade = False
force_bsddb_upgrade = True force_bsddb_upgrade = True
force_bsddb_downgrade = False
else:
self.dbstate.no_database()
break
except gen.db.exceptions.BsddbDowngradeRequiredError, 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: else:
self.dbstate.no_database() self.dbstate.no_database()
break break
# Get here is there is an exception the while loop does not handle # Get here is there is an exception the while loop does not handle
except gen.db.exceptions.BsddbDowngradeError, msg: except gen.db.exceptions.BsddbDowngradeError, msg:
self.dbstate.no_database() self.dbstate.no_database()
self._errordialog( _("Cannot open database"), str(msg)) self._warn( _("Cannot open database"), str(msg))
except gen.db.exceptions.DbVersionError, msg: except gen.db.exceptions.DbVersionError, msg:
self.dbstate.no_database() self.dbstate.no_database()
self._errordialog( _("Cannot open database"), str(msg)) self._errordialog( _("Cannot open database"), str(msg))