When starting with a library that has a corrupted db, offer the user the option of rebuilding the db from backups, instead of trying to repair the db (which is likely to fail anyway)

This commit is contained in:
Kovid Goyal 2011-09-28 18:53:20 -06:00
parent 488069faad
commit 5d9716f271
2 changed files with 49 additions and 66 deletions

View File

@ -5,12 +5,14 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from PyQt4.Qt import QDialog, QLabel, QVBoxLayout, QDialogButtonBox, \ from PyQt4.Qt import (QDialog, QLabel, QVBoxLayout, QDialogButtonBox,
QProgressBar, QSize, QTimer, pyqtSignal, Qt QProgressBar, QSize, QTimer, pyqtSignal, Qt)
from calibre.library.restore import Restore from calibre.library.restore import Restore
from calibre.gui2 import error_dialog, question_dialog, warning_dialog, \ from calibre.gui2 import (error_dialog, question_dialog, warning_dialog,
info_dialog info_dialog)
from calibre import force_unicode
from calibre.constants import filesystem_encoding
class DBRestore(QDialog): class DBRestore(QDialog):
@ -73,6 +75,19 @@ class DBRestore(QDialog):
self.msg.setText(msg) self.msg.setText(msg)
self.pb.setValue(step) self.pb.setValue(step)
def _show_success_msg(restorer, parent=None):
r = restorer
olddb = _('The old database was saved as: %s')%force_unicode(r.olddb,
filesystem_encoding)
if r.errors_occurred:
warning_dialog(parent, _('Success'),
_('Restoring the database succeeded with some warnings'
' click Show details to see the details. %s')%olddb,
det_msg=r.report, show=True)
else:
info_dialog(parent, _('Success'),
_('Restoring database was successful. %s')%olddb, show=True,
show_copy_button=False)
def restore_database(db, parent=None): def restore_database(db, parent=None):
if not question_dialog(parent, _('Are you sure?'), '<p>'+ if not question_dialog(parent, _('Are you sure?'), '<p>'+
@ -102,14 +117,21 @@ def restore_database(db, parent=None):
_('Restoring database failed, click Show details to see details'), _('Restoring database failed, click Show details to see details'),
det_msg=r.tb, show=True) det_msg=r.tb, show=True)
else: else:
if r.errors_occurred: _show_success_msg(r, parent=parent)
warning_dialog(parent, _('Success'),
_('Restoring the database succeeded with some warnings'
' click Show details to see the details.'),
det_msg=r.report, show=True)
else:
info_dialog(parent, _('Success'),
_('Restoring database was successful'), show=True,
show_copy_button=False)
return True return True
def repair_library_at(library_path):
d = DBRestore(None, library_path)
d.exec_()
if d.rejected:
return False
r = d.restorer
if r.tb is not None:
error_dialog(None, _('Failed'),
_('Restoring database failed, click Show details to see details'),
det_msg=r.tb, show=True)
return False
_show_success_msg(r)
return True

View File

@ -4,16 +4,15 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import sys, os, time, socket, traceback import sys, os, time, socket, traceback
from functools import partial from functools import partial
from PyQt4.Qt import QCoreApplication, QIcon, QObject, QTimer, \ from PyQt4.Qt import (QCoreApplication, QIcon, QObject, QTimer,
QThread, pyqtSignal, Qt, QProgressDialog, QString, QPixmap, \ QPixmap, QSplashScreen, QApplication)
QSplashScreen, QApplication
from calibre import prints, plugins from calibre import prints, plugins
from calibre.constants import iswindows, __appname__, isosx, DEBUG, \ from calibre.constants import (iswindows, __appname__, isosx, DEBUG,
filesystem_encoding filesystem_encoding)
from calibre.utils.ipc import ADDRESS, RC from calibre.utils.ipc import ADDRESS, RC
from calibre.gui2 import ORG_NAME, APP_UID, initialize_file_icon_provider, \ from calibre.gui2 import (ORG_NAME, APP_UID, initialize_file_icon_provider,
Application, choose_dir, error_dialog, question_dialog, gprefs Application, choose_dir, error_dialog, question_dialog, gprefs)
from calibre.gui2.main_window import option_parser as _option_parser from calibre.gui2.main_window import option_parser as _option_parser
from calibre.utils.config import prefs, dynamic from calibre.utils.config import prefs, dynamic
from calibre.library.database2 import LibraryDatabase2 from calibre.library.database2 import LibraryDatabase2
@ -110,36 +109,9 @@ def get_library_path(parent=None):
default_dir=get_default_library_path()) default_dir=get_default_library_path())
return library_path return library_path
class DBRepair(QThread): def repair_library(library_path):
from calibre.gui2.dialogs.restore_library import repair_library_at
repair_done = pyqtSignal(object, object) return repair_library_at(library_path)
progress = pyqtSignal(object, object)
def __init__(self, library_path, parent, pd):
QThread.__init__(self, parent)
self.library_path = library_path
self.pd = pd
self.progress.connect(self._callback, type=Qt.QueuedConnection)
def _callback(self, num, is_length):
if is_length:
self.pd.setRange(0, num-1)
num = 0
self.pd.setValue(num)
def callback(self, num, is_length):
self.progress.emit(num, is_length)
def run(self):
from calibre.debug import reinit_db
try:
reinit_db(os.path.join(self.library_path, 'metadata.db'),
self.callback)
db = LibraryDatabase2(self.library_path)
tb = None
except:
db, tb = None, traceback.format_exc()
self.repair_done.emit(db, tb)
class GuiRunner(QObject): class GuiRunner(QObject):
'''Make sure an event loop is running before starting the main work of '''Make sure an event loop is running before starting the main work of
@ -184,9 +156,6 @@ class GuiRunner(QObject):
raise SystemExit(1) raise SystemExit(1)
def initialize_db_stage2(self, db, tb): def initialize_db_stage2(self, db, tb):
repair_pd = getattr(self, 'repair_pd', None)
if repair_pd is not None:
repair_pd.cancel()
if db is None and tb is not None: if db is None and tb is not None:
# DB Repair failed # DB Repair failed
@ -219,23 +188,15 @@ class GuiRunner(QObject):
db = LibraryDatabase2(self.library_path) db = LibraryDatabase2(self.library_path)
except (sqlite.Error, DatabaseException): except (sqlite.Error, DatabaseException):
repair = question_dialog(self.splash_screen, _('Corrupted database'), repair = question_dialog(self.splash_screen, _('Corrupted database'),
_('Your calibre database appears to be corrupted. Do ' _('Your calibre library database appears to be corrupted. Do '
'you want calibre to try and repair it automatically? ' 'you want calibre to try and rebuild it automatically? '
'The rebuild may not be completely successful. '
'If you say No, a new empty calibre library will be created.'), 'If you say No, a new empty calibre library will be created.'),
det_msg=traceback.format_exc() det_msg=traceback.format_exc()
) )
if repair: if repair:
self.repair_pd = QProgressDialog(_('Repairing database. This ' if repair_library(self.library_path):
'can take a very long time for a large collection'), QString(), db = LibraryDatabase2(self.library_path)
0, 0)
self.repair_pd.setWindowModality(Qt.WindowModal)
self.repair_pd.show()
self.repair = DBRepair(self.library_path, self, self.repair_pd)
self.repair.repair_done.connect(self.initialize_db_stage2,
type=Qt.QueuedConnection)
self.repair.start()
return
except: except:
error_dialog(self.splash_screen, _('Bad database location'), error_dialog(self.splash_screen, _('Bad database location'),
_('Bad database location %r. Will start with ' _('Bad database location %r. Will start with '