plugin_manager.py
Go to the documentation of this file.
00001 # Copyright (c) 2011, Dirk Thomas, Dorian Scholz, TU Darmstadt
00002 # All rights reserved.
00003 #
00004 # Redistribution and use in source and binary forms, with or without
00005 # modification, are permitted provided that the following conditions
00006 # are met:
00007 #
00008 #   * Redistributions of source code must retain the above copyright
00009 #     notice, this list of conditions and the following disclaimer.
00010 #   * Redistributions in binary form must reproduce the above
00011 #     copyright notice, this list of conditions and the following
00012 #     disclaimer in the documentation and/or other materials provided
00013 #     with the distribution.
00014 #   * Neither the name of the TU Darmstadt nor the names of its
00015 #     contributors may be used to endorse or promote products derived
00016 #     from this software without specific prior written permission.
00017 #
00018 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00019 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00020 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00021 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00022 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00023 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00024 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00025 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00026 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00027 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00028 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00029 # POSSIBILITY OF SUCH DAMAGE.
00030 
00031 import traceback
00032 
00033 from python_qt_binding.QtCore import qCritical, qDebug, QObject, Qt, Signal, Slot
00034 
00035 from .container_manager import ContainerManager
00036 from .plugin_handler_container import PluginHandlerContainer
00037 from .plugin_handler_direct import PluginHandlerDirect
00038 from .plugin_instance_id import PluginInstanceId
00039 from .plugin_menu import PluginMenu
00040 
00041 
00042 class PluginManager(QObject):
00043 
00044     """
00045     Manager of plugin life cycle.
00046     It creates a specific `PluginHandler` for each plugin instance and maintains the perspective specific set of running plugins.
00047     """
00048 
00049     plugins_about_to_change_signal = Signal()
00050     plugins_changed_signal = Signal()
00051     plugin_help_signal = Signal(object)
00052     save_settings_completed_signal = Signal()
00053     close_application_signal = Signal()
00054     _deferred_reload_plugin_signal = Signal(str)
00055 
00056     def __init__(self, plugin_provider, application_context):
00057         super(PluginManager, self).__init__()
00058         self.setObjectName('PluginManager')
00059 
00060         self._plugin_provider = plugin_provider
00061         self._application_context = application_context
00062 
00063         self._main_window = None
00064         self._container_manager = None
00065         self._plugin_menu = None
00066 
00067         self._global_settings = None
00068         self._perspective_settings = None
00069         self._plugin_descriptors = None
00070         self._running_plugins = {}
00071 
00072         self._number_of_ongoing_calls = None
00073 
00074         if self._application_context.options.multi_process or self._application_context.options.embed_plugin:
00075             try:
00076                 from .plugin_handler_xembed import PluginHandlerXEmbed  # @UnusedImport
00077             except ImportError:
00078                 qCritical('PluginManager.__init__() multiprocess-mode only available under linux')
00079                 exit(-1)
00080 
00081         # force connection type to queued, to delay the 'reloading' giving the 'unloading' time to finish
00082         self._deferred_reload_plugin_signal.connect(self._reload_plugin_load, type=Qt.QueuedConnection)
00083 
00084         if self._application_context.provide_app_dbus_interfaces:
00085             from .plugin_manager_dbus_interface import PluginManagerDBusInterface
00086             self._dbus_service = PluginManagerDBusInterface(self, self._application_context)
00087 
00088     def set_main_window(self, main_window, menu_bar):
00089         self._main_window = main_window
00090         self._container_manager = ContainerManager(self._main_window, self)
00091         self.plugins_changed_signal.connect(self._container_manager.restore_state_of_containers)
00092         if menu_bar is not None:
00093             self._plugin_menu = PluginMenu(menu_bar, self)
00094             self._plugin_menu.load_plugin_signal.connect(self.load_plugin)
00095             self._plugin_menu.unload_plugin_signal.connect(self.unload_plugin)
00096 
00097     def discover(self):
00098         # skip discover if called multiple times
00099         if self._plugin_descriptors is not None:
00100             return
00101         self._plugin_descriptors = {}
00102         # register discovered plugins
00103         plugin_descriptors = self._plugin_provider.discover()
00104         for plugin_descriptor in plugin_descriptors:
00105             self._plugin_descriptors[plugin_descriptor.plugin_id()] = plugin_descriptor
00106 
00107             if self._plugin_menu is not None:
00108                 self._plugin_menu.add_plugin(plugin_descriptor)
00109 
00110         if self._container_manager is not None:
00111             descriptor = self._container_manager.get_container_descriptor()
00112             self._plugin_descriptors[descriptor.plugin_id()] = descriptor
00113             if self._plugin_menu is not None:
00114                 self._container_manager.add_to_plugin_menu(self._plugin_menu)
00115 
00116     def find_plugins_by_name(self, lookup_name):
00117         plugins = {}
00118         for plugin_id, plugin_full_name in self.get_plugins().items():
00119             if plugin_full_name.lower().find(lookup_name.lower()) >= 0 or plugin_id.lower().find(lookup_name.lower()) >= 0:
00120                 plugins[plugin_id] = plugin_full_name
00121         return plugins
00122 
00123     def get_plugins(self):
00124         self.discover()
00125         plugins = {}
00126         for plugin_id, plugin_descriptor in self._plugin_descriptors.items():
00127             plugin_name_parts = []
00128             plugin_name = plugin_descriptor.attributes().get('plugin_name', None)
00129             if plugin_name is not None:
00130                 plugin_name_parts.append(plugin_name)
00131             plugin_name_parts += plugin_descriptor.attributes().get('class_type', 'unknown').split('::')
00132             plugin_full_name = '/'.join(plugin_name_parts)
00133             plugins[plugin_id] = plugin_full_name
00134         return plugins
00135 
00136     def is_plugin_running(self, plugin_id, serial_number):
00137         instance_id = PluginInstanceId(plugin_id, serial_number)
00138         return str(instance_id) in self._running_plugins
00139 
00140 
00141     @Slot(str)
00142     @Slot(str, int)
00143     def load_plugin(self, plugin_id, serial_number=None, argv=None):
00144         qDebug('PluginManager.load_plugin(%s, %s)' % (str(plugin_id), str(serial_number) if serial_number is not None else ''))
00145         # save state of top-level widgets
00146         self.plugins_about_to_change_signal.emit()
00147         if serial_number is None:
00148             serial_number = self._next_serial_number(plugin_id)
00149         instance_id = PluginInstanceId(plugin_id, serial_number)
00150         self._load_plugin_load(instance_id, self._load_plugin_restore, argv)
00151 
00152     def _next_serial_number(self, plugin_id):
00153         # convert from unicode
00154         plugin_id = str(plugin_id)
00155         # collect serial numbers of all running instances of the specific plugin
00156         used_serial_numbers = {}
00157         for info in self._running_plugins.values():
00158             if info['instance_id'].plugin_id == plugin_id:
00159                 used_serial_numbers[info['instance_id'].serial_number] = None
00160 
00161         # find first not used serial number
00162         serial_number = 1
00163         while serial_number in used_serial_numbers:
00164             serial_number = serial_number + 1
00165         return serial_number
00166 
00167     def _load_plugin_load(self, instance_id, callback, argv=None):
00168         # if the requested instance is already running, do nothing
00169         if str(instance_id) in self._running_plugins:
00170             raise Exception('PluginManager._load_plugin(%s) instance already loaded' % str(instance_id))
00171 
00172         # containers are pseudo-plugins and handled by a special handler
00173         if self._container_manager is not None and instance_id.plugin_id == self._container_manager.get_container_descriptor().plugin_id():
00174             handler = PluginHandlerContainer(self, self._main_window, instance_id, self._application_context, self._container_manager)
00175 
00176         # use platform specific handler for multiprocess-mode if available
00177         elif self._application_context.options.multi_process or self._application_context.options.embed_plugin:
00178             try:
00179                 from .plugin_handler_xembed import PluginHandlerXEmbed
00180                 handler = PluginHandlerXEmbed(self, self._main_window, instance_id, self._application_context, self._container_manager, argv)
00181             except ImportError:
00182                 qCritical('PluginManager._load_plugin() could not load plugin in a separate process')
00183                 return
00184 
00185         # use direct handler for in-process plugins
00186         else:
00187             handler = PluginHandlerDirect(self, self._main_window, instance_id, self._application_context, self._container_manager, argv)
00188 
00189         self._add_running_plugin(instance_id, handler)
00190         handler.load(self._plugin_provider, callback)
00191 
00192     def _add_running_plugin(self, instance_id, handler):
00193         if self._plugin_menu is not None:
00194             plugin_descriptor = self._plugin_descriptors[instance_id.plugin_id]
00195             self._plugin_menu.add_instance(plugin_descriptor, instance_id)
00196 
00197         info = {
00198             'handler': handler,
00199             'instance_id': instance_id
00200         }
00201         self._running_plugins[str(instance_id)] = info
00202 
00203     def _load_plugin_restore(self, handler, exception):
00204         qDebug('PluginManager._load_plugin_restore()')
00205         self._load_plugin_completed(handler, exception)
00206         if exception is None:
00207             # restore settings after load
00208             self._restore_plugin_settings(handler.instance_id(), self._emit_load_plugin_completed)
00209 
00210     def _load_plugin_completed(self, handler, exception):
00211         instance_id = handler.instance_id()
00212         if exception is not None:
00213             qCritical('PluginManager._load_plugin() could not load plugin "%s"%s' % (instance_id.plugin_id, (':\n%s' % traceback.format_exc() if exception != True else '')))
00214             self._remove_running_plugin(instance_id)
00215             # quit embed application
00216             if self._application_context.options.embed_plugin:
00217                 exit(-1)
00218             return
00219 
00220         qDebug('PluginManager._load_plugin(%s) successful' % str(instance_id))
00221 
00222         handler.close_signal.connect(self.unload_plugin)
00223         handler.reload_signal.connect(self.reload_plugin)
00224         handler.help_signal.connect(self._emit_plugin_help_signal)
00225 
00226     def _emit_plugin_help_signal(self, instance_id_str):
00227         instance_id = PluginInstanceId(instance_id=instance_id_str)
00228         plugin_descriptor = self._plugin_descriptors[instance_id.plugin_id]
00229         self.plugin_help_signal.emit(plugin_descriptor)
00230 
00231     def _restore_plugin_settings(self, instance_id, callback):
00232         if self._global_settings is not None and self._perspective_settings is not None:
00233             info = self._running_plugins[str(instance_id)]
00234             plugin_settings = self._global_settings.get_settings('plugin__' + instance_id.tidy_plugin_str())
00235             instance_settings = self._perspective_settings.get_settings('plugin__' + instance_id.tidy_str())
00236             handler = info['handler']
00237             handler.restore_settings(plugin_settings, instance_settings, callback)
00238         else:
00239             callback(instance_id)
00240 
00241     def _emit_load_plugin_completed(self, instance_id):
00242         qDebug('PluginManager._emit_load_plugin_completed()')
00243         # restore state of top-level widgets
00244         self.plugins_changed_signal.emit()
00245 
00246 
00247     @Slot(str)
00248     def unload_plugin(self, instance_id_str):
00249         # unloading a plugin with locked perspective or running standalone triggers close of application
00250         if self._application_context.options.lock_perspective or self._application_context.options.standalone_plugin:
00251             self.close_application_signal.emit()
00252             return
00253         instance_id = PluginInstanceId(instance_id=instance_id_str)
00254         qDebug('PluginManager.unload_plugin(%s)' % str(instance_id))
00255         # save state of top-level widgets
00256         self.plugins_about_to_change_signal.emit()
00257         # save settings before shutdown and unloading
00258         self._save_plugin_settings(instance_id, self._unload_plugin_shutdown)
00259 
00260     def _save_plugin_settings(self, instance_id, callback):
00261         if self._global_settings is not None and self._perspective_settings is not None:
00262             info = self._running_plugins[str(instance_id)]
00263             plugin_settings = self._global_settings.get_settings('plugin__' + instance_id.tidy_plugin_str())
00264             instance_settings = self._perspective_settings.get_settings('plugin__' + instance_id.tidy_str())
00265             handler = info['handler']
00266             handler.save_settings(plugin_settings, instance_settings, callback)
00267         else:
00268             callback(instance_id)
00269 
00270     def _unload_plugin_shutdown(self, instance_id):
00271         qDebug('PluginManager._unload_plugin_shutdown(%s)' % str(instance_id))
00272         self._shutdown_plugin(instance_id, self._unload_plugin_unload)
00273 
00274     def _shutdown_plugin(self, instance_id, callback):
00275         # shutdown plugin before unloading
00276         info = self._running_plugins[str(instance_id)]
00277         handler = info['handler']
00278         handler.close_signal.disconnect(self.unload_plugin)
00279         handler.shutdown_plugin(callback)
00280 
00281     def _unload_plugin_unload(self, instance_id):
00282         qDebug('PluginManager._unload_plugin_unload(%s)' % str(instance_id))
00283         self._unload_plugin(instance_id, self._unload_plugin_completed)
00284 
00285     def _unload_plugin(self, instance_id, callback=None):
00286         # unload plugin
00287         info = self._running_plugins[str(instance_id)]
00288         handler = info['handler']
00289         handler.unload(callback)
00290 
00291     def _unload_plugin_completed(self, instance_id):
00292         qDebug('PluginManager._unload_plugin_completed(%s)' % str(instance_id))
00293         self._remove_running_plugin(instance_id)
00294 
00295     def _remove_running_plugin(self, instance_id):
00296         if self._plugin_menu is not None:
00297             self._plugin_menu.remove_instance(instance_id)
00298         info = self._running_plugins[str(instance_id)]
00299         self._running_plugins.pop(str(instance_id))
00300 
00301 
00302     @Slot(str)
00303     def reload_plugin(self, instance_id_str):
00304         instance_id = PluginInstanceId(instance_id=instance_id_str)
00305         qDebug('PluginManager.reload_plugin(%s)' % str(instance_id))
00306         # save state of top-level widgets
00307         self.plugins_about_to_change_signal.emit()
00308         self._reload_plugin_save(instance_id)
00309 
00310     def _reload_plugin_save(self, instance_id):
00311         # save settings before unloading
00312         self._save_plugin_settings(instance_id, self._reload_plugin_shutdown)
00313 
00314     def _reload_plugin_shutdown(self, instance_id):
00315         qDebug('PluginManager._reload_plugin_shutdown(%s)' % str(instance_id))
00316         self._shutdown_plugin(instance_id, self._reload_plugin_unload)
00317 
00318     def _reload_plugin_unload(self, instance_id):
00319         qDebug('PluginManager._reload_plugin_unload(%s)' % str(instance_id))
00320         self._unload_plugin(instance_id, self._reload_plugin_schedule_load)
00321 
00322     def _reload_plugin_schedule_load(self, instance_id):
00323         qDebug('PluginManager._reload_plugin_schedule_load(%s)' % str(instance_id))
00324         self._remove_running_plugin(instance_id)
00325         self._deferred_reload_plugin_signal.emit(str(instance_id))
00326 
00327     def _reload_plugin_load(self, instance_id_str):
00328         instance_id = PluginInstanceId(instance_id=instance_id_str)
00329         qDebug('PluginManager._reload_plugin_load(%s)' % str(instance_id))
00330         self._load_plugin_load(instance_id, self._reload_plugin_restore)
00331 
00332     def _reload_plugin_restore(self, handler, exception):
00333         qDebug('PluginManager._reload_plugin_restore()')
00334         self._load_plugin_completed(handler, exception)
00335         if exception is None:
00336             # restore settings after load
00337             self._restore_plugin_settings(handler.instance_id(), self._emit_load_plugin_completed)
00338 
00339 
00340     def save_settings(self, global_settings, perspective_settings):
00341         self._save_settings(global_settings, perspective_settings, self._save_settings_callback)
00342 
00343     def _save_settings(self, global_settings, perspective_settings, callback):
00344         qDebug('PluginManager.save_settings()')
00345         self._global_settings = global_settings.get_settings('pluginmanager')
00346         self._perspective_settings = perspective_settings.get_settings('pluginmanager')
00347         self._store_running_plugins()
00348         # trigger async call on all running plugins
00349         self._number_of_ongoing_calls = len(self._running_plugins)
00350         if self._number_of_ongoing_calls > 0:
00351             for info in self._running_plugins.values():
00352                 self._save_plugin_settings(info['instance_id'], callback)
00353         else:
00354             callback()
00355 
00356     def _store_running_plugins(self):
00357         if self._perspective_settings is not None:
00358             plugins = {}
00359             for info in self._running_plugins.values():
00360                 instance_id = info['instance_id']
00361                 plugin_id = instance_id.plugin_id
00362                 if plugin_id not in plugins:
00363                     plugins[plugin_id] = []
00364                 plugins[plugin_id].append(instance_id.serial_number)
00365             self._perspective_settings.set_value('running-plugins', plugins)
00366 
00367     def _save_settings_callback(self, instance_id=None):
00368         if instance_id is not None:
00369             self._number_of_ongoing_calls = self._number_of_ongoing_calls - 1
00370         if self._number_of_ongoing_calls == 0:
00371             qDebug('PluginManager.save_settings() completed')
00372             self._number_of_ongoing_calls = None
00373             self.save_settings_completed_signal.emit()
00374 
00375 
00376     def close_application(self, global_settings, perspective_settings):
00377         self._save_settings(global_settings, perspective_settings, self._close_application_save_callback)
00378 
00379     def _close_application_save_callback(self, instance_id=None):
00380         self._save_settings_callback(instance_id)
00381         if self._number_of_ongoing_calls is None:
00382             self._close_application_shutdown_plugins()
00383 
00384     def _close_application_shutdown_plugins(self):
00385         # trigger async call on all running plugins
00386         self._number_of_ongoing_calls = len(self._running_plugins)
00387         if self._number_of_ongoing_calls > 0:
00388             for info in self._running_plugins.values():
00389                 self._shutdown_plugin(info['instance_id'], self._close_application_shutdown_callback)
00390         else:
00391             self._close_application_shutdown_callback()
00392 
00393     def _close_application_shutdown_callback(self, instance_id=None):
00394         if instance_id is not None:
00395             self._number_of_ongoing_calls = self._number_of_ongoing_calls - 1
00396         if self._number_of_ongoing_calls == 0:
00397             qDebug('PluginManager.close_application() completed')
00398             self._number_of_ongoing_calls = None
00399             self.close_application_signal.emit()
00400 
00401 
00402     def restore_settings(self, global_settings, perspective_settings):
00403         qDebug('PluginManager.restore_settings()')
00404         self._global_settings = global_settings.get_settings('pluginmanager')
00405         self._perspective_settings = perspective_settings.get_settings('pluginmanager')
00406         self._restore_settings_save_obsolete()
00407 
00408     def _restore_settings_save_obsolete(self):
00409         # trigger shutdown of obsolete plugins
00410         plugins = self._restore_running_plugins_get_plugins()
00411         obsolete = []
00412         for instance_id in self._running_plugins.keys():
00413             if instance_id not in plugins.keys():
00414                 obsolete.append(PluginInstanceId(instance_id=instance_id))
00415         self._number_of_ongoing_calls = len(obsolete)
00416         if self._number_of_ongoing_calls > 0:
00417             qDebug('PluginManager.restore_settings() unloading %d obsolete plugins' % self._number_of_ongoing_calls)
00418             for instance_id in obsolete:
00419                 self._shutdown_plugin(instance_id, self._restore_settings_unload_obsolete)
00420         else:
00421             self._restore_settings_unload_obsolete_callback()
00422 
00423     def _restore_running_plugins_get_plugins(self):
00424         plugins = {}
00425         if self._perspective_settings is not None:
00426             data = self._perspective_settings.value('running-plugins', {})
00427             for plugin_id, serial_numbers in data.items():
00428                 for serial_number in serial_numbers:
00429                     instance_id = PluginInstanceId(plugin_id, serial_number)
00430                     plugins[str(instance_id)] = instance_id
00431         return plugins
00432 
00433     def _restore_settings_unload_obsolete(self, instance_id):
00434         # trigger unload of obsolete plugins
00435         self._unload_plugin(instance_id, self._restore_settings_unload_obsolete_callback)
00436 
00437     def _restore_settings_unload_obsolete_callback(self, instance_id=None):
00438         if instance_id is not None:
00439             self._number_of_ongoing_calls = self._number_of_ongoing_calls - 1
00440             self._remove_running_plugin(instance_id)
00441         if self._number_of_ongoing_calls == 0:
00442             if instance_id is not None:
00443                 qDebug('PluginManager.restore_settings() all obsolete plugins unloaded')
00444             self._number_of_ongoing_calls = None
00445             self._restore_settings_load_missing()
00446 
00447     def _restore_settings_load_missing(self):
00448         # trigger_load of not yet loaded plugins
00449         plugins = self._restore_running_plugins_get_plugins()
00450         loading = []
00451         for instance_id_str, instance_id in plugins.items():
00452             if instance_id_str not in self._running_plugins.keys():
00453                 loading.append(instance_id)
00454         self._number_of_ongoing_calls = len(loading)
00455         if self._number_of_ongoing_calls > 0:
00456             qDebug('PluginManager.restore_settings() loading %d plugins' % self._number_of_ongoing_calls)
00457             for instance_id in loading:
00458                 self._load_plugin_load(instance_id, self._restore_settings_load_missing_callback)
00459         else:
00460             self._restore_settings_load_missing_callback()
00461 
00462     def _restore_settings_load_missing_callback(self, handler=None, exception=None):
00463         if handler is not None:
00464             self._number_of_ongoing_calls = self._number_of_ongoing_calls - 1
00465             self._load_plugin_completed(handler, exception)
00466         if self._number_of_ongoing_calls == 0:
00467             if handler is not None:
00468                 qDebug('PluginManager.restore_settings() all missing plugins loaded')
00469             self._number_of_ongoing_calls = None
00470             self._restore_settings_restore()
00471 
00472     def restore_settings_without_plugins(self, global_settings, perspective_settings):
00473         qDebug('PluginManager.restore_settings_without_plugins()')
00474         self._global_settings = global_settings.get_settings('pluginmanager')
00475         self._perspective_settings = perspective_settings.get_settings('pluginmanager')
00476         self._restore_settings_restore()
00477 
00478     def _restore_settings_restore(self):
00479         # trigger restore settings for all running plugins
00480         self._number_of_ongoing_calls = len(self._running_plugins)
00481         if self._number_of_ongoing_calls > 0:
00482             for info in self._running_plugins.values():
00483                 self._restore_plugin_settings(info['instance_id'], self._restore_settings_restore_callback)
00484         else:
00485             self._restore_settings_restore_callback()
00486 
00487     def _restore_settings_restore_callback(self, instance_id=None):
00488         if instance_id is not None:
00489             self._number_of_ongoing_calls = self._number_of_ongoing_calls - 1
00490         if self._number_of_ongoing_calls == 0:
00491             if instance_id is not None:
00492                 qDebug('PluginManager.restore_settings() all plugin settings restored')
00493             self._number_of_ongoing_calls = None
00494             # restore state of top-level widgets
00495             self.plugins_changed_signal.emit()


qt_gui
Author(s): Dirk Thomas
autogenerated on Fri Jan 3 2014 11:44:00