diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 957b11de2e..5e490b55bf 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1286,9 +1286,9 @@ class DB: finally: self.reopen() - def vacuum(self): + def vacuum(self, include_fts_db): self.execute('VACUUM') - if self.fts_enabled: + if self.fts_enabled and include_fts_db: self.fts.vacuum() @property diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 243d4c026f..5990a5e2bc 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2468,10 +2468,10 @@ class Cache: return self.backend.dump_and_restore(callback=callback, sql=sql) @write_api - def vacuum(self): + def vacuum(self, include_fts_db=False): self.is_doing_rebuild_or_vacuum = True try: - self.backend.vacuum() + self.backend.vacuum(include_fts_db) finally: self.is_doing_rebuild_or_vacuum = False diff --git a/src/calibre/db/cli/cmd_check_library.py b/src/calibre/db/cli/cmd_check_library.py index 4c3153baa8..59852c29e3 100644 --- a/src/calibre/db/cli/cmd_check_library.py +++ b/src/calibre/db/cli/cmd_check_library.py @@ -60,6 +60,13 @@ Perform some checks on the filesystem representing a library. Reports are {0} help=_("Comma-separated list of names to ignore.\n" "Default: all") ) + parser.add_option( + '--vacuum-fts-db', + default=False, + action='store_true', + help=_('Vacuum the full text search database. This can be very slow and memory intensive, depending on the size of the database.') + ) + return parser @@ -113,7 +120,7 @@ def main(opts, args, dbctx): db = LibraryDatabase(dbctx.library_path) prints(_('Vacuuming database...')) - db.new_api.vacuum() + db.new_api.vacuum(opts.vacuum_fts_db) checker = CheckLibrary(dbctx.library_path, db) checker.scan_library(names, exts) for check in checks: diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index b6cf8903a5..6333b787f4 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -658,10 +658,7 @@ class ChooseLibraryAction(InterfaceAction): d = DBCheck(self.gui, db) try: - d.start() - from calibre.gui2.widgets import BusyCursor - with BusyCursor(): - d.exec() + d.exec() try: m.close() except Exception: diff --git a/src/calibre/gui2/dialogs/check_library.py b/src/calibre/gui2/dialogs/check_library.py index 0625b7eef7..e39e889d2b 100644 --- a/src/calibre/gui2/dialogs/check_library.py +++ b/src/calibre/gui2/dialogs/check_library.py @@ -6,50 +6,82 @@ __docformat__ = 'restructuredtext en' __license__ = 'GPL v3' import os +import weakref +from qt.core import ( + QApplication, QCheckBox, QCursor, QDialog, QDialogButtonBox, QGridLayout, + QHBoxLayout, QIcon, QLabel, QLineEdit, QProgressBar, QPushButton, QSplitter, + QStackedLayout, Qt, QTextEdit, QTreeWidget, QTreeWidgetItem, QVBoxLayout, + QWidget, pyqtSignal +) from threading import Thread -from qt.core import ( - QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QLabel, QPushButton, - QApplication, QTreeWidgetItem, QLineEdit, Qt, QSize, - QIcon, QTextEdit, QSplitter, QWidget, QGridLayout, pyqtSignal) - +from calibre import as_unicode, prints from calibre.gui2.dialogs.confirm_delete import confirm -from calibre.library.check_library import CheckLibrary, CHECKS +from calibre.library.check_library import CHECKS, CheckLibrary from calibre.utils.recycle_bin import delete_file, delete_tree -from calibre import prints, as_unicode class DBCheck(QDialog): # {{{ - update_msg = pyqtSignal(object) finished_vacuum = pyqtSignal() def __init__(self, parent, db): QDialog.__init__(self, parent) - self.l = QVBoxLayout() - self.setLayout(self.l) - self.l1 = QLabel(_('Vacuuming database to improve performance.') + ' ' + - _('This will take a while, please wait...')) - self.setWindowTitle(_('Vacuuming...')) - self.l1.setWordWrap(True) - self.l.addWidget(self.l1) - self.msg = QLabel('') - self.update_msg.connect(self.msg.setText, type=Qt.ConnectionType.QueuedConnection) + self.vacuum_started = False self.finished_vacuum.connect(self.accept, type=Qt.ConnectionType.QueuedConnection) - self.l.addWidget(self.msg) - self.msg.setWordWrap(True) - self.resize(self.sizeHint() + QSize(100, 50)) self.error = None - self.db = db.new_api self.rejected = False + s = QStackedLayout(self) + s.setContentsMargins(0, 0, 0, 0) + one = QWidget(self) + s.addWidget(one) + two = QWidget(self) + s.addWidget(two) + + l = QVBoxLayout(one) + la = QLabel(_('Check database integrity and compact it for improved performance.')) + la.setWordWrap(True) + l.addWidget(la) + + self.fts = f = QCheckBox(_('Also compact the Full text search database')) + l.addWidget(f) + la = QLabel('

' + _( + 'This can be a very slow and memory intensive operation,' + ' depending on the size of the Full text database.')) + la.setWordWrap(True) + l.addWidget(la) + l.addStretch(10) + self.bb1 = bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, self) + l.addWidget(bb) + bb.accepted.connect(self.start) + bb.rejected.connect(self.reject) + self.setWindowTitle(_('Check the database file')) + + l = QVBoxLayout(two) + la = QLabel(_('Vacuuming database to improve performance.') + ' ' + + _('This will take a while, please wait...')) + la.setWordWrap(True) + l.addWidget(la) + pb = QProgressBar(self) + l.addWidget(pb) + pb.setMinimum(0), pb.setMaximum(0) + l.addStretch(10) + self.resize(self.sizeHint()) + self.db = weakref.ref(db.new_api) + def start(self): - t = self.thread = Thread(target=self.vacuum, daemon=True, name='VacuumDB') + self.setWindowTitle(_('Vacuuming...')) + self.layout().setCurrentIndex(1) + QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor)) + self.vacuum_started = True + db = self.db() + t = self.thread = Thread(target=self.vacuum, args=(db, self.fts.isChecked()), daemon=True, name='VacuumDB') t.start() - def vacuum(self): + def vacuum(self, db, include_fts_db): try: - self.db.vacuum() + db.vacuum(include_fts_db) except Exception as e: import traceback self.error = (as_unicode(e), traceback.format_exc()) @@ -57,10 +89,20 @@ class DBCheck(QDialog): # {{{ def reject(self): self.rejected = True + if self.vacuum_started: + return return QDialog.reject(self) + def closeEvent(self, ev): + if self.vacuum_started: + ev.ignore() + return + return super().closeEvent(ev) + def break_cycles(self): - self.db = self.thread = None + if self.vacuum_started: + QApplication.restoreOverrideCursor() + self.thread = None # }}} @@ -458,7 +500,8 @@ class CheckLibraryDialog(QDialog): if __name__ == '__main__': - app = QApplication([]) - from calibre.library import db - d = CheckLibraryDialog(None, db()) + from calibre.gui2 import Application + app = Application([]) + from calibre.library import db as dbconn + d = DBCheck(None, dbconn()) d.exec()