From 22ca6dcbba2a759d0d4c7abf33b5b56275867774 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 28 Mar 2016 12:50:15 +0200 Subject: [PATCH] Add a LibraryClosed plugin type that is called when the library is closed (new_api). Add a way to provide a message during shutdown. --- src/calibre/customize/__init__.py | 22 +++++++++++++++++++ src/calibre/customize/ui.py | 13 ++++++++++- src/calibre/db/cache.py | 7 ++++++ src/calibre/gui2/layout.py | 36 ++++++++++++++++++++++++++----- src/calibre/gui2/ui.py | 20 ++++++++++++++++- 5 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 8f244d4ecc..4323ae11d0 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 because it requires the new db + minimum_calibre_version = (2, 0, 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..c827f71edd 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,16 @@ 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 +# }}} + # Store Plugins # {{{ def store_plugins(): diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index c4f351a4a0..9e28503bba 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: + 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..ca097aa39d 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) @@ -306,8 +306,34 @@ class MainWindowMixin(object): # {{{ l = self.centralwidget.layout() l.addWidget(self.search_bar) + + + def show_shutdown_message(self, message): + msgs = getattr(self, 'shutdown_messages', None) + if msgs is None: + msgs = self.shutdown_messages = [] + msgs.append(message) + + smw = QWidget() + sml = QVBoxLayout() + smw.setLayout(sml) + + # Construct the widget containing all the messages to date. Add stretch + # to make it vertically centered. + sml.addStretch() + for msg in msgs: + sml.addWidget(QLabel(msg), alignment=Qt.AlignHCenter) + sml.addStretch() + + # The next line is needed to prevent the main widget from being garbage + # collected just in case more processing is required (and it is). As we + # are shutting down, the memory leak isn't of concern + if getattr(self, 'saved_central_widget', None) is None: + self.saved_central_widget = self.centralWidget + + # Show the shutdown messages + self.setCentralWidget(smw) + # 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..5fa193534b 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): + get_gui().show_shutdown_message(_('Shutting down')) + + from calibre.customize.ui import available_library_closed_plugins + if available_library_closed_plugins(): + get_gui().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,20 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ if mb is not None: mb.stop() - self.hide_windows() + if db is not None: + db.close() + + # This happens again later. Don't do it here because it hides the + # shutdown messages + # self.hide_windows() 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. + get_gui().show_shutdown_message( + _('Shutting down the content server. This could take a while ...')) s = self.content_server self.content_server = None s.exit()