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 os
00032 import time
00033 import traceback
00034
00035 from python_qt_binding.QtCore import qCritical, qDebug, QObject, QSettings, Qt, qWarning, Signal, Slot
00036
00037 from .errors import PluginLoadError
00038 from .plugin_handler_container import PluginHandlerContainer
00039 from .plugin_handler_direct import PluginHandlerDirect
00040 from .plugin_instance_id import PluginInstanceId
00041 from .plugin_menu import PluginMenu
00042 from .settings import Settings
00043 from .settings_proxy import SettingsProxy
00044
00045
00046 class PluginManager(QObject):
00047
00048 """
00049 Manager of plugin life cycle.
00050 It creates a specific `PluginHandler` for each plugin instance and maintains the perspective specific set of running plugins.
00051 """
00052
00053 plugins_about_to_change_signal = Signal()
00054 plugins_changed_signal = Signal()
00055 plugin_help_signal = Signal(object)
00056 save_settings_completed_signal = Signal()
00057 close_application_signal = Signal()
00058 _deferred_reload_plugin_signal = Signal(str)
00059
00060 discovery_cache_max_age = 60 * 60 * 24
00061
00062 def __init__(self, plugin_provider, settings, application_context, settings_prefix=''):
00063 super(PluginManager, self).__init__()
00064 self.setObjectName('PluginManager')
00065
00066 self._plugin_provider = plugin_provider
00067 self._settings = Settings(SettingsProxy(settings), '/'.join([x for x in ['plugin_manager', settings_prefix] if x != '']))
00068 self._application_context = application_context
00069
00070 self._main_window = None
00071 self._container_manager = None
00072 self._plugin_menu = None
00073 self._minimized_dock_widgets_toolbar = None
00074
00075 self._global_settings = None
00076 self._perspective_settings = None
00077 self._plugin_descriptors = None
00078 self._running_plugins = {}
00079
00080 self._number_of_ongoing_calls = None
00081
00082 if self._application_context.options.multi_process or self._application_context.options.embed_plugin:
00083 try:
00084 from .plugin_handler_xembed import PluginHandlerXEmbed
00085 except ImportError:
00086 qCritical('PluginManager.__init__() multiprocess-mode only available under linux')
00087 exit(-1)
00088
00089
00090 self._deferred_reload_plugin_signal.connect(self._reload_plugin_load, type=Qt.QueuedConnection)
00091
00092 if self._application_context.provide_app_dbus_interfaces:
00093 from .plugin_manager_dbus_interface import PluginManagerDBusInterface
00094 self._dbus_service = PluginManagerDBusInterface(self, self._application_context)
00095
00096 def set_main_window(self, main_window, menu_bar, container_manager):
00097 self._main_window = main_window
00098 self._container_manager = container_manager
00099 self.plugins_changed_signal.connect(self._container_manager.restore_state_of_containers)
00100 if menu_bar is not None:
00101 self._plugin_menu = PluginMenu(menu_bar, self)
00102 self._plugin_menu.load_plugin_signal.connect(self.load_plugin)
00103 self._plugin_menu.unload_plugin_signal.connect(self.unload_plugin)
00104
00105 def set_minimized_dock_widgets_toolbar(self, toolbar):
00106 self._minimized_dock_widgets_toolbar = toolbar
00107
00108 def discover(self):
00109
00110 if self._plugin_descriptors is not None:
00111 return
00112 self._plugin_descriptors = {}
00113
00114
00115 plugin_descriptors = self._discover()
00116 for plugin_descriptor in plugin_descriptors:
00117 self._plugin_descriptors[plugin_descriptor.plugin_id()] = plugin_descriptor
00118
00119 if self._plugin_menu is not None:
00120 self._plugin_menu.add_plugin(plugin_descriptor)
00121
00122 if self._container_manager is not None:
00123 descriptor = self._container_manager.get_container_descriptor()
00124 self._plugin_descriptors[descriptor.plugin_id()] = descriptor
00125 if self._plugin_menu is not None:
00126 self._container_manager.add_to_plugin_menu(self._plugin_menu)
00127
00128 def _discover(self):
00129 discovery_data = self._settings.get_settings('discovery_data')
00130
00131 cache_stamp = self._settings.value('discovery_timestamp', default_value=None)
00132 if cache_stamp:
00133 cache_stamp = float(cache_stamp)
00134
00135
00136 now = time.time()
00137 if self._application_context.options.force_discover or not cache_stamp or cache_stamp > now or cache_stamp + PluginManager.discovery_cache_max_age < now:
00138 qDebug('PluginManager._discover() force discovery of plugins')
00139 cache_stamp = None
00140 for k in discovery_data.all_keys():
00141 discovery_data.remove(k)
00142 else:
00143 qDebug('PluginManager._discover() using cached plugin discovery information')
00144
00145 plugin_descriptors = self._plugin_provider.discover(discovery_data)
00146
00147
00148 if not cache_stamp:
00149 self._settings.set_value('discovery_timestamp', now)
00150
00151 return plugin_descriptors
00152
00153 def find_plugins_by_name(self, lookup_name):
00154 plugins = {}
00155 for plugin_id, plugin_full_name in self.get_plugins().items():
00156 if plugin_full_name.lower().find(lookup_name.lower()) >= 0 or plugin_id.lower().find(lookup_name.lower()) >= 0:
00157 plugins[plugin_id] = plugin_full_name
00158 return plugins
00159
00160 def get_plugins(self):
00161 self.discover()
00162 plugins = {}
00163 for plugin_id, plugin_descriptor in self._plugin_descriptors.items():
00164 plugins[plugin_id] = '/'.join(plugin_descriptor.attributes().get('class_type', 'unknown').split('::'))
00165 return plugins
00166
00167 def is_plugin_running(self, plugin_id, serial_number):
00168 instance_id = PluginInstanceId(plugin_id, serial_number)
00169 return str(instance_id) in self._running_plugins
00170
00171
00172 @Slot(str)
00173 @Slot(str, int)
00174 def load_plugin(self, plugin_id, serial_number=None, argv=None):
00175 qDebug('PluginManager.load_plugin(%s, %s)' % (str(plugin_id), str(serial_number) if serial_number is not None else ''))
00176
00177 self.plugins_about_to_change_signal.emit()
00178 if serial_number is None:
00179 serial_number = self._next_serial_number(plugin_id)
00180 instance_id = PluginInstanceId(plugin_id, serial_number)
00181 self._load_plugin_load(instance_id, self._load_plugin_restore, argv)
00182
00183 def _next_serial_number(self, plugin_id):
00184
00185 plugin_id = str(plugin_id)
00186
00187 used_serial_numbers = {}
00188 for info in self._running_plugins.values():
00189 if info['instance_id'].plugin_id == plugin_id:
00190 used_serial_numbers[info['instance_id'].serial_number] = None
00191
00192
00193 serial_number = 1
00194 while serial_number in used_serial_numbers:
00195 serial_number = serial_number + 1
00196 return serial_number
00197
00198 def _load_plugin_load(self, instance_id, callback, argv=None):
00199
00200 if str(instance_id) in self._running_plugins:
00201 raise Exception('PluginManager._load_plugin(%s) instance already loaded' % str(instance_id))
00202
00203
00204 if self._container_manager is not None and instance_id.plugin_id == self._container_manager.get_container_descriptor().plugin_id():
00205 handler = PluginHandlerContainer(self, self._main_window, instance_id, self._application_context, self._container_manager)
00206
00207
00208 elif self._application_context.options.multi_process or self._application_context.options.embed_plugin:
00209 try:
00210 from .plugin_handler_xembed import PluginHandlerXEmbed
00211 handler = PluginHandlerXEmbed(self, self._main_window, instance_id, self._application_context, self._container_manager, argv)
00212 except ImportError:
00213 qCritical('PluginManager._load_plugin() could not load plugin in a separate process')
00214 return
00215
00216
00217 else:
00218 handler = PluginHandlerDirect(self, self._main_window, instance_id, self._application_context, self._container_manager, argv)
00219
00220 handler.set_minimized_dock_widgets_toolbar(self._minimized_dock_widgets_toolbar)
00221
00222 plugin_descriptor = self._plugin_descriptors[instance_id.plugin_id]
00223 handler.set_plugin_descriptor(plugin_descriptor)
00224
00225 self._add_running_plugin(instance_id, handler)
00226 handler.load(self._plugin_provider, callback)
00227
00228 def _add_running_plugin(self, instance_id, handler):
00229 if self._plugin_menu is not None:
00230 plugin_descriptor = self._plugin_descriptors[instance_id.plugin_id]
00231 self._plugin_menu.add_instance(plugin_descriptor, instance_id)
00232
00233 info = {
00234 'handler': handler,
00235 'instance_id': instance_id
00236 }
00237 self._running_plugins[str(instance_id)] = info
00238
00239 def _load_plugin_restore(self, handler, exception):
00240 qDebug('PluginManager._load_plugin_restore()')
00241 self._load_plugin_completed(handler, exception)
00242 if exception is None:
00243
00244 self._restore_plugin_settings(handler.instance_id(), self._emit_load_plugin_completed)
00245
00246 def _load_plugin_completed(self, handler, exception):
00247 instance_id = handler.instance_id()
00248 if exception is not None:
00249 if isinstance(exception, PluginLoadError):
00250 qWarning('PluginManager._load_plugin() could not load plugin "%s": %s' % (instance_id.plugin_id, exception))
00251 else:
00252 qCritical('PluginManager._load_plugin() could not load plugin "%s"%s' % (instance_id.plugin_id, (':\n%s' % traceback.format_exc() if exception != True else '')))
00253 self._remove_running_plugin(instance_id)
00254
00255 if self._application_context.options.embed_plugin:
00256 exit(-1)
00257 return
00258
00259 qDebug('PluginManager._load_plugin(%s) successful' % str(instance_id))
00260
00261 handler.close_signal.connect(self.unload_plugin)
00262 handler.reload_signal.connect(self.reload_plugin)
00263 handler.help_signal.connect(self._emit_plugin_help_signal)
00264
00265 def _emit_plugin_help_signal(self, instance_id_str):
00266 instance_id = PluginInstanceId(instance_id=instance_id_str)
00267 plugin_descriptor = self._plugin_descriptors[instance_id.plugin_id]
00268 self.plugin_help_signal.emit(plugin_descriptor)
00269
00270 def _restore_plugin_settings(self, instance_id, callback):
00271 if self._global_settings is not None and self._perspective_settings is not None:
00272 info = self._running_plugins[str(instance_id)]
00273 plugin_settings = self._global_settings.get_settings('plugin__' + instance_id.tidy_plugin_str())
00274 instance_settings = self._perspective_settings.get_settings('plugin__' + instance_id.tidy_str())
00275 handler = info['handler']
00276 handler.restore_settings(plugin_settings, instance_settings, callback)
00277 else:
00278 callback(instance_id)
00279
00280 def _emit_load_plugin_completed(self, instance_id):
00281 qDebug('PluginManager._emit_load_plugin_completed()')
00282
00283 self.plugins_changed_signal.emit()
00284
00285
00286 @Slot(str)
00287 def unload_plugin(self, instance_id_str):
00288
00289 if self._application_context.options.lock_perspective or self._application_context.options.standalone_plugin:
00290 self._close_application_signal()
00291 return
00292 instance_id = PluginInstanceId(instance_id=instance_id_str)
00293 qDebug('PluginManager.unload_plugin(%s)' % str(instance_id))
00294
00295 self.plugins_about_to_change_signal.emit()
00296
00297 self._save_plugin_settings(instance_id, self._unload_plugin_shutdown)
00298
00299 def _save_plugin_settings(self, instance_id, callback):
00300 if self._global_settings is not None and self._perspective_settings is not None:
00301 info = self._running_plugins[str(instance_id)]
00302 plugin_settings = self._global_settings.get_settings('plugin__' + instance_id.tidy_plugin_str())
00303 instance_settings = self._perspective_settings.get_settings('plugin__' + instance_id.tidy_str())
00304 handler = info['handler']
00305 handler.save_settings(plugin_settings, instance_settings, callback)
00306 else:
00307 callback(instance_id)
00308
00309 def _unload_plugin_shutdown(self, instance_id):
00310 qDebug('PluginManager._unload_plugin_shutdown(%s)' % str(instance_id))
00311 self._shutdown_plugin(instance_id, self._unload_plugin_unload)
00312
00313 def _shutdown_plugin(self, instance_id, callback):
00314
00315 info = self._running_plugins[str(instance_id)]
00316 handler = info['handler']
00317 handler.close_signal.disconnect(self.unload_plugin)
00318 handler.shutdown_plugin(callback)
00319
00320 def _unload_plugin_unload(self, instance_id):
00321 qDebug('PluginManager._unload_plugin_unload(%s)' % str(instance_id))
00322 self._unload_plugin(instance_id, self._unload_plugin_completed)
00323
00324 def _unload_plugin(self, instance_id, callback=None):
00325
00326 info = self._running_plugins[str(instance_id)]
00327 handler = info['handler']
00328 handler.unload(callback)
00329
00330 def _unload_plugin_completed(self, instance_id):
00331 qDebug('PluginManager._unload_plugin_completed(%s)' % str(instance_id))
00332 self._remove_running_plugin(instance_id)
00333
00334 def _remove_running_plugin(self, instance_id):
00335 if self._plugin_menu is not None:
00336 self._plugin_menu.remove_instance(instance_id)
00337 info = self._running_plugins[str(instance_id)]
00338 self._running_plugins.pop(str(instance_id))
00339
00340
00341 @Slot(str)
00342 def reload_plugin(self, instance_id_str):
00343 instance_id = PluginInstanceId(instance_id=instance_id_str)
00344 qDebug('PluginManager.reload_plugin(%s)' % str(instance_id))
00345
00346 self.plugins_about_to_change_signal.emit()
00347 self._reload_plugin_save(instance_id)
00348
00349 def _reload_plugin_save(self, instance_id):
00350
00351 self._save_plugin_settings(instance_id, self._reload_plugin_shutdown)
00352
00353 def _reload_plugin_shutdown(self, instance_id):
00354 qDebug('PluginManager._reload_plugin_shutdown(%s)' % str(instance_id))
00355 self._shutdown_plugin(instance_id, self._reload_plugin_unload)
00356
00357 def _reload_plugin_unload(self, instance_id):
00358 qDebug('PluginManager._reload_plugin_unload(%s)' % str(instance_id))
00359 self._unload_plugin(instance_id, self._reload_plugin_schedule_load)
00360
00361 def _reload_plugin_schedule_load(self, instance_id):
00362 qDebug('PluginManager._reload_plugin_schedule_load(%s)' % str(instance_id))
00363 self._remove_running_plugin(instance_id)
00364 self._deferred_reload_plugin_signal.emit(str(instance_id))
00365
00366 def _reload_plugin_load(self, instance_id_str):
00367 instance_id = PluginInstanceId(instance_id=instance_id_str)
00368 qDebug('PluginManager._reload_plugin_load(%s)' % str(instance_id))
00369 self._load_plugin_load(instance_id, self._reload_plugin_restore)
00370
00371 def _reload_plugin_restore(self, handler, exception):
00372 qDebug('PluginManager._reload_plugin_restore()')
00373 self._load_plugin_completed(handler, exception)
00374 if exception is None:
00375
00376 self._restore_plugin_settings(handler.instance_id(), self._emit_load_plugin_completed)
00377
00378
00379 def save_settings(self, global_settings, perspective_settings):
00380 self._save_settings(global_settings, perspective_settings, self._save_settings_callback)
00381
00382 def _save_settings(self, global_settings, perspective_settings, callback):
00383 qDebug('PluginManager.save_settings()')
00384 self._global_settings = global_settings.get_settings('pluginmanager')
00385 self._perspective_settings = perspective_settings.get_settings('pluginmanager')
00386 self._store_running_plugins()
00387
00388 self._number_of_ongoing_calls = len(self._running_plugins)
00389 if self._number_of_ongoing_calls > 0:
00390 for info in self._running_plugins.values():
00391 self._save_plugin_settings(info['instance_id'], callback)
00392 else:
00393 callback()
00394
00395 def _store_running_plugins(self):
00396 if self._perspective_settings is not None:
00397 plugins = {}
00398 for info in self._running_plugins.values():
00399 instance_id = info['instance_id']
00400 plugin_id = instance_id.plugin_id
00401 if plugin_id not in plugins:
00402 plugins[plugin_id] = []
00403 plugins[plugin_id].append(instance_id.serial_number)
00404 self._perspective_settings.set_value('running-plugins', plugins)
00405
00406 def _save_settings_callback(self, instance_id=None):
00407 if instance_id is not None:
00408 self._number_of_ongoing_calls = self._number_of_ongoing_calls - 1
00409 if self._number_of_ongoing_calls == 0:
00410 qDebug('PluginManager.save_settings() completed')
00411 self._number_of_ongoing_calls = None
00412 self.save_settings_completed_signal.emit()
00413
00414
00415 def close_application(self, global_settings, perspective_settings):
00416 self._save_settings(global_settings, perspective_settings, self._close_application_save_callback)
00417
00418 def _close_application_save_callback(self, instance_id=None):
00419 self._save_settings_callback(instance_id)
00420 if self._number_of_ongoing_calls is None:
00421 self._close_application_shutdown_plugins()
00422
00423 def _close_application_shutdown_plugins(self):
00424
00425 self._number_of_ongoing_calls = len(self._running_plugins)
00426 if self._number_of_ongoing_calls > 0:
00427 for info in self._running_plugins.values():
00428 self._shutdown_plugin(info['instance_id'], self._close_application_shutdown_callback)
00429 else:
00430 self._close_application_shutdown_callback()
00431
00432 def _close_application_shutdown_callback(self, instance_id=None):
00433 if instance_id is not None:
00434 self._number_of_ongoing_calls = self._number_of_ongoing_calls - 1
00435 if self._number_of_ongoing_calls == 0:
00436 qDebug('PluginManager.close_application() completed')
00437 self._number_of_ongoing_calls = None
00438 self._close_application_signal()
00439
00440 def _close_application_signal(self):
00441 self._plugin_provider.shutdown()
00442 self.close_application_signal.emit()
00443
00444
00445 def restore_settings(self, global_settings, perspective_settings):
00446 qDebug('PluginManager.restore_settings()')
00447 self._global_settings = global_settings.get_settings('pluginmanager')
00448 self._perspective_settings = perspective_settings.get_settings('pluginmanager')
00449 self._restore_settings_save_obsolete()
00450
00451 def _restore_settings_save_obsolete(self):
00452
00453 plugins = self._restore_running_plugins_get_plugins()
00454 obsolete = []
00455 for instance_id in self._running_plugins.keys():
00456 if instance_id not in plugins.keys():
00457 obsolete.append(PluginInstanceId(instance_id=instance_id))
00458 self._number_of_ongoing_calls = len(obsolete)
00459 if self._number_of_ongoing_calls > 0:
00460 qDebug('PluginManager.restore_settings() unloading %d obsolete plugins' % self._number_of_ongoing_calls)
00461 for instance_id in obsolete:
00462 self._shutdown_plugin(instance_id, self._restore_settings_unload_obsolete)
00463 else:
00464 self._restore_settings_unload_obsolete_callback()
00465
00466 def _restore_running_plugins_get_plugins(self):
00467 plugins = {}
00468 if self._perspective_settings is not None:
00469 data = self._perspective_settings.value('running-plugins', {})
00470 for plugin_id, serial_numbers in data.items():
00471 for serial_number in serial_numbers:
00472 instance_id = PluginInstanceId(plugin_id, serial_number)
00473 plugins[str(instance_id)] = instance_id
00474 return plugins
00475
00476 def _restore_settings_unload_obsolete(self, instance_id):
00477
00478 self._unload_plugin(instance_id, self._restore_settings_unload_obsolete_callback)
00479
00480 def _restore_settings_unload_obsolete_callback(self, instance_id=None):
00481 if instance_id is not None:
00482 self._number_of_ongoing_calls = self._number_of_ongoing_calls - 1
00483 self._remove_running_plugin(instance_id)
00484 if self._number_of_ongoing_calls == 0:
00485 if instance_id is not None:
00486 qDebug('PluginManager.restore_settings() all obsolete plugins unloaded')
00487 self._number_of_ongoing_calls = None
00488 self._restore_settings_load_missing()
00489
00490 def _restore_settings_load_missing(self):
00491
00492 plugins = self._restore_running_plugins_get_plugins()
00493 loading = []
00494 for instance_id_str, instance_id in plugins.items():
00495 if instance_id_str not in self._running_plugins.keys():
00496 loading.append(instance_id)
00497 self._number_of_ongoing_calls = len(loading)
00498 if self._number_of_ongoing_calls > 0:
00499 qDebug('PluginManager.restore_settings() loading %d plugins' % self._number_of_ongoing_calls)
00500 for instance_id in loading:
00501 self._load_plugin_load(instance_id, self._restore_settings_load_missing_callback)
00502 else:
00503 self._restore_settings_load_missing_callback()
00504
00505 def _restore_settings_load_missing_callback(self, handler=None, exception=None):
00506 if handler is not None:
00507 self._number_of_ongoing_calls = self._number_of_ongoing_calls - 1
00508 self._load_plugin_completed(handler, exception)
00509 if self._number_of_ongoing_calls == 0:
00510 if handler is not None:
00511 qDebug('PluginManager.restore_settings() all missing plugins loaded')
00512 self._number_of_ongoing_calls = None
00513 self._restore_settings_restore()
00514
00515 def restore_settings_without_plugins(self, global_settings, perspective_settings):
00516 qDebug('PluginManager.restore_settings_without_plugins()')
00517 self._global_settings = global_settings.get_settings('pluginmanager')
00518 self._perspective_settings = perspective_settings.get_settings('pluginmanager')
00519 self._restore_settings_restore()
00520
00521 def _restore_settings_restore(self):
00522
00523 self._number_of_ongoing_calls = len(self._running_plugins)
00524 if self._number_of_ongoing_calls > 0:
00525 for info in self._running_plugins.values():
00526 self._restore_plugin_settings(info['instance_id'], self._restore_settings_restore_callback)
00527 else:
00528 self._restore_settings_restore_callback()
00529
00530 def _restore_settings_restore_callback(self, instance_id=None):
00531 if instance_id is not None:
00532 self._number_of_ongoing_calls = self._number_of_ongoing_calls - 1
00533 if self._number_of_ongoing_calls == 0:
00534 if instance_id is not None:
00535 qDebug('PluginManager.restore_settings() all plugin settings restored')
00536 self._number_of_ongoing_calls = None
00537
00538 self.plugins_changed_signal.emit()