From 22ca6dcbba2a759d0d4c7abf33b5b56275867774 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 28 Mar 2016 12:50:15 +0200 Subject: [PATCH 1/3] 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() From f705a5311c2caa3b260c297ef0d8aa20cc4ddadf Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 28 Mar 2016 17:08:10 +0200 Subject: [PATCH 2/3] Don't replace the central widget when displaying shutdown messages, instead use a widget already in the layout. This will prevent widgets from being scavenged. --- src/calibre/gui2/layout.py | 41 ++++++++++++++------------------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index ca097aa39d..42fa272bf1 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -304,36 +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): - 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) + 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() - # }}} From 5cd9a5bf9deb45e771c5de14e350b347483840ed Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 28 Mar 2016 17:50:04 +0200 Subject: [PATCH 3/3] Changes requested: use Exception in db.close(), change minimum version, add a method to test for existence of LibraryClosed plugins. Note that the plugins cannot actually be tested with this commit because calibre's version is 2.53. --- src/calibre/customize/__init__.py | 4 ++-- src/calibre/customize/ui.py | 7 +++++++ src/calibre/db/cache.py | 2 +- src/calibre/gui2/ui.py | 4 ++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 4323ae11d0..e35fd722e5 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -789,8 +789,8 @@ class LibraryClosedPlugin(Plugin): # {{{ ''' type = _('Library Closed') - # minimum version 2 because it requires the new db - minimum_calibre_version = (2, 0, 0) + # minimum version 2.54 because that is when support was added + minimum_calibre_version = (2, 54, 0) def run(self, db): ''' diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index c827f71edd..ad7f861344 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -255,6 +255,13 @@ def available_library_closed_plugins(): 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 # {{{ diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 9e28503bba..611efc80f8 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2017,7 +2017,7 @@ class Cache(object): for plugin in available_library_closed_plugins(): try: plugin.run(self) - except: + except Exception: import traceback traceback.print_exc() self.backend.close() diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 5fa193534b..700e9554ae 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -880,8 +880,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ 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(): + from calibre.customize.ui import has_library_closed_plugins + if has_library_closed_plugins(): get_gui().show_shutdown_message( _('Running database shutdown plugins. This could take a few seconds...'))