00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
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
00077 except ImportError:
00078 qCritical('PluginManager.__init__() multiprocess-mode only available under linux')
00079 exit(-1)
00080
00081
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
00099 if self._plugin_descriptors is not None:
00100 return
00101 self._plugin_descriptors = {}
00102
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
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
00154 plugin_id = str(plugin_id)
00155
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
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
00169 if str(instance_id) in self._running_plugins:
00170 raise Exception('PluginManager._load_plugin(%s) instance already loaded' % str(instance_id))
00171
00172
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
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
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
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
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
00244 self.plugins_changed_signal.emit()
00245
00246
00247 @Slot(str)
00248 def unload_plugin(self, instance_id_str):
00249
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
00256 self.plugins_about_to_change_signal.emit()
00257
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
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
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
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
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
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
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
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
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
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
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
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
00495 self.plugins_changed_signal.emit()