diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index fd20d88049..7034380a56 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -16,7 +16,6 @@ from calibre.utils.config import prefs from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \ question_dialog, info_dialog from calibre.gui2.actions import InterfaceAction -from calibre.gui2.dialogs.check_library import CheckLibraryDialog, DBCheck class LibraryUsageStats(object): # {{{ @@ -139,6 +138,12 @@ class ChooseLibraryAction(InterfaceAction): None, None), attr='action_check_library') ac.triggered.connect(self.check_library, type=Qt.QueuedConnection) self.maintenance_menu.addAction(ac) + ac = self.create_action(spec=(_('Restore database'), 'lt.png', + None, None), + attr='action_restore_database') + ac.triggered.connect(self.restore_database, type=Qt.QueuedConnection) + self.maintenance_menu.addAction(ac) + self.choose_menu.addMenu(self.maintenance_menu) def pick_random(self, *args): @@ -267,7 +272,17 @@ class ChooseLibraryAction(InterfaceAction): _('Metadata will be backed up while calibre is running, at the ' 'rate of approximately 1 book every three seconds.'), show=True) + def restore_database(self): + from calibre.gui2.dialogs.restore_library import restore_database + m = self.gui.library_view.model() + m.stop_metadata_backup() + db = m.db + db.prefs.disable_setting = True + if restore_database(db, self.gui): + self.gui.library_moved(db.library_path, call_close=False) + def check_library(self): + from calibre.gui2.dialogs.check_library import CheckLibraryDialog, DBCheck self.gui.library_view.save_state() m = self.gui.library_view.model() m.stop_metadata_backup() diff --git a/src/calibre/gui2/dialogs/restore_library.py b/src/calibre/gui2/dialogs/restore_library.py new file mode 100644 index 0000000000..dd1befc11b --- /dev/null +++ b/src/calibre/gui2/dialogs/restore_library.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QDialog, QLabel, QVBoxLayout, QDialogButtonBox, \ + QProgressBar, QSize, QTimer, pyqtSignal, Qt + +from calibre.library.restore import Restore +from calibre.gui2 import error_dialog, question_dialog, warning_dialog, \ + info_dialog + +class DBRestore(QDialog): + + update_signal = pyqtSignal(object, object) + + def __init__(self, parent, library_path): + QDialog.__init__(self, parent) + self.l = QVBoxLayout() + self.setLayout(self.l) + self.l1 = QLabel(''+_('Restoring database from backups, do not' + ' interrupt, this will happen in two stages')+'...') + self.setWindowTitle(_('Restoring database')) + self.l.addWidget(self.l1) + self.pb = QProgressBar(self) + self.l.addWidget(self.pb) + self.pb.setMaximum(0) + self.pb.setMinimum(0) + self.msg = QLabel('') + self.l.addWidget(self.msg) + self.msg.setWordWrap(True) + self.bb = QDialogButtonBox(QDialogButtonBox.Cancel) + self.l.addWidget(self.bb) + self.bb.rejected.connect(self.reject) + self.resize(self.sizeHint() + QSize(100, 50)) + self.error = None + self.rejected = False + self.library_path = library_path + self.update_signal.connect(self.do_update, type=Qt.QueuedConnection) + + self.restorer = Restore(library_path, self) + self.restorer.daemon = True + + # Give the metadata backup thread time to stop + QTimer.singleShot(2000, self.start) + + + def start(self): + self.restorer.start() + QTimer.singleShot(10, self.update) + + def reject(self): + self.rejected = True + self.restorer.progress_callback = lambda x, y: x + QDialog.rejecet(self) + + def update(self): + if self.restorer.is_alive(): + QTimer.singleShot(10, self.update) + else: + self.restorer.progress_callback = lambda x, y: x + self.accept() + + def __call__(self, msg, step): + self.update_signal.emit(msg, step) + + def do_update(self, msg, step): + if msg is None: + self.pb.setMaximum(step) + else: + self.msg.setText(msg) + self.pb.setValue(step) + + +def restore_database(db, parent=None): + if not question_dialog(parent, _('Are you sure?'), '

'+ + _('Your list of books, with all their metadata is ' + 'stored in a single file, called a database. ' + 'In addition, metadata for each individual ' + 'book is stored in that books\' folder, as ' + 'a backup.' + '

This operation will rebuild ' + 'the database from the individual book ' + 'metadata. This is useful if the ' + 'database has been corrupted and you get a ' + 'blank list of books. Note that restoring only ' + 'restores books, not any settings stored in the ' + 'database, or any custom recipes.' + '

Do you want to restore the database?')): + return False + db.conn.close() + d = DBRestore(parent, db.library_path) + d.exec_() + r = d.restorer + d.restorer = None + if d.rejected: + return True + if r.tb is not None: + error_dialog(parent, _('Failed'), + _('Restoring database failed, click Show details to see details'), + det_msg=r.tb, show=True) + else: + if r.errors_occurred: + 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 +