diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 8f244d4ecc..e35fd722e5 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -781,3 +781,25 @@ class EditBookToolPlugin(Plugin): # {{{ # }}} +class LibraryClosedPlugin(Plugin): # {{{ + ''' + LibraryClosedPlugins are run when a library is closed, either at shutdown, + when the library is changed, or when a library is used in some other way. + At the moment these plugins won't be called by the CLI functions. + ''' + type = _('Library Closed') + + # minimum version 2.54 because that is when support was added + minimum_calibre_version = (2, 54, 0) + + def run(self, db): + ''' + The db will be a reference to the new_api (db.cache.py). + + The plugin must run to completion. It must not use the GUI, threads, or + any signals. + ''' + raise NotImplementedError('LibraryClosedPlugin ' + 'run method must be overridden in subclass') +# }}} + diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index 43306ebedc..ad7f861344 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -9,7 +9,8 @@ from calibre.customize import (CatalogPlugin, FileTypePlugin, PluginNotFound, MetadataReaderPlugin, MetadataWriterPlugin, InterfaceActionBase as InterfaceAction, PreferencesPlugin, platform, InvalidPlugin, - StoreBase as Store, ViewerPlugin, EditBookToolPlugin) + StoreBase as Store, ViewerPlugin, EditBookToolPlugin, + LibraryClosedPlugin) from calibre.customize.conversion import InputFormatPlugin, OutputFormatPlugin from calibre.customize.zipplugin import loader from calibre.customize.profiles import InputProfile, OutputProfile @@ -246,6 +247,23 @@ def preferences_plugins(): yield plugin # }}} +# Library Closed Plugins # {{{ +def available_library_closed_plugins(): + customization = config['plugin_customization'] + for plugin in _initialized_plugins: + if isinstance(plugin, LibraryClosedPlugin): + if not is_disabled(plugin): + plugin.site_customization = customization.get(plugin.name, '') + yield plugin + +def has_library_closed_plugins(): + for plugin in _initialized_plugins: + if isinstance(plugin, LibraryClosedPlugin): + if not is_disabled(plugin): + return True + return False +# }}} + # Store Plugins # {{{ def store_plugins(): diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index c4f351a4a0..611efc80f8 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2013,6 +2013,13 @@ class Cache(object): @write_api def close(self): + from calibre.customize.ui import available_library_closed_plugins + for plugin in available_library_closed_plugins(): + try: + plugin.run(self) + except Exception: + import traceback + traceback.print_exc() self.backend.close() @write_api diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index b009d3fa2c..42fa272bf1 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' from functools import partial from PyQt5.Qt import (QIcon, Qt, QWidget, QSize, - pyqtSignal, QToolButton, QMenu, QAction, + pyqtSignal, QToolButton, QMenu, QAction, QCoreApplication, QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup) @@ -304,10 +304,25 @@ class MainWindowMixin(object): # {{{ pass # PyQt5 seems to be missing this property l = self.centralwidget.layout() + + # Add in the widget for the shutdown messages. It is invisible until a + # message is shown + smw = self.shutdown_message_widget = QLabel('') + smw.setMinimumHeight(200) + smw.setAlignment(Qt.AlignCenter) + self.shutdown_message_widget.setVisible(False) + l.addWidget(smw) + + # And now, start adding the real widgets l.addWidget(self.search_bar) + + def show_shutdown_message(self, message): + smw = self.shutdown_message_widget + smw.setVisible(True) + txt = smw.text() + txt += '\n' + message + smw.setText(txt) + # Force processing the events needed to show the message + QCoreApplication.processEvents() # }}} - - - - diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 02f0037d59..b19e2de365 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -878,7 +878,15 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ return True def shutdown(self, write_settings=True): + self.show_shutdown_message(_('Shutting down')) + + from calibre.customize.ui import has_library_closed_plugins + if has_library_closed_plugins(): + self.show_shutdown_message( + _('Running database shutdown plugins. This could take a few seconds...')) + self.grid_view.shutdown() + db = None try: db = self.library_view.model().db cf = db.clean @@ -908,10 +916,17 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ if mb is not None: mb.stop() - self.hide_windows() + if db is not None: + db.close() + try: try: if self.content_server is not None: + # If the content server has any sockets being closed then + # this can take quite a long time (minutes). Tell the user that it is + # happening. + self.show_shutdown_message( + _('Shutting down the content server. This could take a while ...')) s = self.content_server self.content_server = None s.exit() @@ -919,15 +934,15 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ pass except KeyboardInterrupt: pass + self.hide_windows() + # Do not report any errors that happen after the shutdown + sys.excepthook = sys.__excepthook__ if self._spare_pool is not None: self._spare_pool.shutdown() from calibre.db.delete_service import shutdown shutdown() time.sleep(2) self.istores.join() - self.hide_windows() - # Do not report any errors that happen after the shutdown - sys.excepthook = sys.__excepthook__ return True def run_wizard(self, *args):