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 get_plugin_descriptor(self, plugin_id):
00168 return self._plugin_descriptors.get(plugin_id, None)
00169
00170 def is_plugin_running(self, plugin_id, serial_number):
00171 instance_id = PluginInstanceId(plugin_id, serial_number)
00172 return str(instance_id) in self._running_plugins
00173
00174
00175 @Slot(str)
00176 @Slot(str, int)
00177 def load_plugin(self, plugin_id, serial_number=None, argv=None):
00178 qDebug('PluginManager.load_plugin(%s, %s)' % (str(plugin_id), str(serial_number) if serial_number is not None else ''))
00179
00180 self.plugins_about_to_change_signal.emit()
00181 if serial_number is None:
00182 serial_number = self._next_serial_number(plugin_id)
00183 instance_id = PluginInstanceId(plugin_id, serial_number)
00184 self._load_plugin_load(instance_id, self._load_plugin_restore, argv)
00185
00186 def _next_serial_number(self, plugin_id):
00187
00188 plugin_id = str(plugin_id)
00189
00190 used_serial_numbers = {}
00191 for info in self._running_plugins.values():
00192 if info['instance_id'].plugin_id == plugin_id:
00193 used_serial_numbers[info['instance_id'].serial_number] = None
00194
00195
00196 serial_number = 1
00197 while serial_number in used_serial_numbers:
00198 serial_number = serial_number + 1
00199 return serial_number
00200
00201 def _load_plugin_load(self, instance_id, callback, argv=None):
00202
00203 if str(instance_id) in self._running_plugins:
00204 raise Exception('PluginManager._load_plugin(%s) instance already loaded' % str(instance_id))
00205
00206
00207 if self._container_manager is not None and instance_id.plugin_id == self._container_manager.get_container_descriptor().plugin_id():
00208 handler = PluginHandlerContainer(self, self._main_window, instance_id, self._application_context, self._container_manager)
00209
00210
00211 elif self._application_context.options.multi_process or self._application_context.options.embed_plugin:
00212 try:
00213 from .plugin_handler_xembed import PluginHandlerXEmbed
00214 handler = PluginHandlerXEmbed(self, self._main_window, instance_id, self._application_context, self._container_manager, argv)
00215 except ImportError:
00216 qCritical('PluginManager._load_plugin() could not load plugin in a separate process')
00217 return
00218
00219
00220 else:
00221 handler = PluginHandlerDirect(self, self._main_window, instance_id, self._application_context, self._container_manager, argv)
00222
00223 handler.set_minimized_dock_widgets_toolbar(self._minimized_dock_widgets_toolbar)
00224
00225 plugin_descriptor = self._plugin_descriptors[instance_id.plugin_id]
00226 handler.set_plugin_descriptor(plugin_descriptor)
00227
00228 self._add_running_plugin(instance_id, handler)
00229 handler.load(self._plugin_provider, callback)
00230
00231 def _add_running_plugin(self, instance_id, handler):
00232 if self._plugin_menu is not None:
00233 plugin_descriptor = self._plugin_descriptors[instance_id.plugin_id]
00234 self._plugin_menu.add_instance(plugin_descriptor, instance_id)
00235 handler.label_updated.connect(self._plugin_menu.update_plugin_instance_label)
00236
00237 info = {
00238 'handler': handler,
00239 'instance_id': instance_id
00240 }
00241 self._running_plugins[str(instance_id)] = info
00242
00243 def _load_plugin_restore(self, handler, exception):
00244 qDebug('PluginManager._load_plugin_restore()')
00245 self._load_plugin_completed(handler, exception)
00246 if exception is None:
00247
00248 self._restore_plugin_settings(handler.instance_id(), self._emit_load_plugin_completed)
00249
00250 def _load_plugin_completed(self, handler, exception):
00251 instance_id = handler.instance_id()
00252 if exception is not None:
00253 if isinstance(exception, PluginLoadError):
00254 qWarning('PluginManager._load_plugin() could not load plugin "%s": %s' % (instance_id.plugin_id, exception))
00255 else:
00256 qCritical('PluginManager._load_plugin() could not load plugin "%s"%s' % (instance_id.plugin_id, (':\n%s' % traceback.format_exc() if exception != True else '')))
00257 self._remove_running_plugin(instance_id)
00258
00259 if self._application_context.options.embed_plugin:
00260 exit(-1)
00261 return
00262
00263 qDebug('PluginManager._load_plugin(%s) successful' % str(instance_id))
00264
00265 handler.close_signal.connect(self.unload_plugin)
00266 handler.reload_signal.connect(self.reload_plugin)
00267 handler.help_signal.connect(self._emit_plugin_help_signal)
00268
00269 def _emit_plugin_help_signal(self, instance_id_str):
00270 instance_id = PluginInstanceId(instance_id=instance_id_str)
00271 plugin_descriptor = self._plugin_descriptors[instance_id.plugin_id]
00272 self.plugin_help_signal.emit(plugin_descriptor)
00273
00274 def _restore_plugin_settings(self, instance_id, callback):
00275 if self._global_settings is not None and self._perspective_settings is not None:
00276 info = self._running_plugins[str(instance_id)]
00277 plugin_settings = self._global_settings.get_settings('plugin__' + instance_id.tidy_plugin_str())
00278 instance_settings = self._perspective_settings.get_settings('plugin__' + instance_id.tidy_str())
00279 handler = info['handler']
00280 handler.restore_settings(plugin_settings, instance_settings, callback)
00281 else:
00282 callback(instance_id)
00283
00284 def _emit_load_plugin_completed(self, instance_id):
00285 qDebug('PluginManager._emit_load_plugin_completed()')
00286
00287 self.plugins_changed_signal.emit()
00288
00289
00290 @Slot(str)
00291 def unload_plugin(self, instance_id_str):
00292
00293 if self._application_context.options.lock_perspective or self._application_context.options.standalone_plugin:
00294 self._close_application_signal()
00295 return
00296 instance_id = PluginInstanceId(instance_id=instance_id_str)
00297 qDebug('PluginManager.unload_plugin(%s)' % str(instance_id))
00298
00299 self.plugins_about_to_change_signal.emit()
00300
00301 self._save_plugin_settings(instance_id, self._unload_plugin_shutdown)
00302
00303 def _save_plugin_settings(self, instance_id, callback):
00304 if self._global_settings is not None and self._perspective_settings is not None:
00305 info = self._running_plugins[str(instance_id)]
00306 plugin_settings = self._global_settings.get_settings('plugin__' + instance_id.tidy_plugin_str())
00307 instance_settings = self._perspective_settings.get_settings('plugin__' + instance_id.tidy_str())
00308 handler = info['handler']
00309 handler.save_settings(plugin_settings, instance_settings, callback)
00310 else:
00311 callback(instance_id)
00312
00313 def _unload_plugin_shutdown(self, instance_id):
00314 qDebug('PluginManager._unload_plugin_shutdown(%s)' % str(instance_id))
00315 self._shutdown_plugin(instance_id, self._unload_plugin_unload)
00316
00317 def _shutdown_plugin(self, instance_id, callback):
00318
00319 info = self._running_plugins[str(instance_id)]
00320 handler = info['handler']
00321 handler.close_signal.disconnect(self.unload_plugin)
00322 handler.shutdown_plugin(callback)
00323
00324 def _unload_plugin_unload(self, instance_id):
00325 qDebug('PluginManager._unload_plugin_unload(%s)' % str(instance_id))
00326 self._unload_plugin(instance_id, self._unload_plugin_completed)
00327
00328 def _unload_plugin(self, instance_id, callback=None):
00329
00330 info = self._running_plugins[str(instance_id)]
00331 handler = info['handler']
00332 handler.unload(callback)
00333
00334 def _unload_plugin_completed(self, instance_id):
00335 qDebug('PluginManager._unload_plugin_completed(%s)' % str(instance_id))
00336 self._remove_running_plugin(instance_id)
00337
00338 def _remove_running_plugin(self, instance_id):
00339 info = self._running_plugins[str(instance_id)]
00340 if self._plugin_menu is not None:
00341 self._plugin_menu.remove_instance(instance_id)
00342 info['handler'].label_updated.disconnect(self._plugin_menu.update_plugin_instance_label)
00343 self._running_plugins.pop(str(instance_id))
00344
00345
00346 @Slot(str)
00347 def reload_plugin(self, instance_id_str):
00348 instance_id = PluginInstanceId(instance_id=instance_id_str)
00349 qDebug('PluginManager.reload_plugin(%s)' % str(instance_id))
00350
00351 self.plugins_about_to_change_signal.emit()
00352 self._reload_plugin_save(instance_id)
00353
00354 def _reload_plugin_save(self, instance_id):
00355
00356 self._save_plugin_settings(instance_id, self._reload_plugin_shutdown)
00357
00358 def _reload_plugin_shutdown(self, instance_id):
00359 qDebug('PluginManager._reload_plugin_shutdown(%s)' % str(instance_id))
00360 self._shutdown_plugin(instance_id, self._reload_plugin_unload)
00361
00362 def _reload_plugin_unload(self, instance_id):
00363 qDebug('PluginManager._reload_plugin_unload(%s)' % str(instance_id))
00364 self._unload_plugin(instance_id, self._reload_plugin_schedule_load)
00365
00366 def _reload_plugin_schedule_load(self, instance_id):
00367 qDebug('PluginManager._reload_plugin_schedule_load(%s)' % str(instance_id))
00368 self._remove_running_plugin(instance_id)
00369 self._deferred_reload_plugin_signal.emit(str(instance_id))
00370
00371 def _reload_plugin_load(self, instance_id_str):
00372 instance_id = PluginInstanceId(instance_id=instance_id_str)
00373 qDebug('PluginManager._reload_plugin_load(%s)' % str(instance_id))
00374 self._load_plugin_load(instance_id, self._reload_plugin_restore)
00375
00376 def _reload_plugin_restore(self, handler, exception):
00377 qDebug('PluginManager._reload_plugin_restore()')
00378 self._load_plugin_completed(handler, exception)
00379 if exception is None:
00380
00381 self._restore_plugin_settings(handler.instance_id(), self._emit_load_plugin_completed)
00382
00383
00384 def save_settings(self, global_settings, perspective_settings):
00385 self._save_settings(global_settings, perspective_settings, self._save_settings_callback)
00386
00387 def _save_settings(self, global_settings, perspective_settings, callback):
00388 qDebug('PluginManager.save_settings()')
00389 self._global_settings = global_settings.get_settings('pluginmanager')
00390 self._perspective_settings = perspective_settings.get_settings('pluginmanager')
00391 self._store_running_plugins()
00392
00393 self._number_of_ongoing_calls = len(self._running_plugins)
00394 if self._number_of_ongoing_calls > 0:
00395 for info in self._running_plugins.values():
00396 self._save_plugin_settings(info['instance_id'], callback)
00397 else:
00398 callback()
00399
00400 def _store_running_plugins(self):
00401 if self._perspective_settings is not None:
00402 plugins = {}
00403 for info in self._running_plugins.values():
00404 instance_id = info['instance_id']
00405 plugin_id = instance_id.plugin_id
00406 if plugin_id not in plugins:
00407 plugins[plugin_id] = []
00408 plugins[plugin_id].append(instance_id.serial_number)
00409 self._perspective_settings.set_value('running-plugins', plugins)
00410
00411 def _save_settings_callback(self, instance_id=None):
00412 if instance_id is not None:
00413 self._number_of_ongoing_calls = self._number_of_ongoing_calls - 1
00414 if self._number_of_ongoing_calls == 0:
00415 qDebug('PluginManager.save_settings() completed')
00416 self._number_of_ongoing_calls = None
00417 self.save_settings_completed_signal.emit()
00418
00419
00420 def close_application(self, global_settings, perspective_settings):
00421 self._save_settings(global_settings, perspective_settings, self._close_application_save_callback)
00422
00423 def _close_application_save_callback(self, instance_id=None):
00424 self._save_settings_callback(instance_id)
00425 if self._number_of_ongoing_calls is None:
00426 self._close_application_shutdown_plugins()
00427
00428 def _close_application_shutdown_plugins(self):
00429
00430 self._number_of_ongoing_calls = len(self._running_plugins)
00431 if self._number_of_ongoing_calls > 0:
00432 for info in self._running_plugins.values():
00433 self._shutdown_plugin(info['instance_id'], self._close_application_shutdown_callback)
00434 else:
00435 self._close_application_shutdown_callback()
00436
00437 def _close_application_shutdown_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 if self._number_of_ongoing_calls == 0:
00441 qDebug('PluginManager.close_application() completed')
00442 self._number_of_ongoing_calls = None
00443 self._close_application_signal()
00444
00445 def _close_application_signal(self):
00446 self._plugin_provider.shutdown()
00447 self.close_application_signal.emit()
00448
00449
00450 def restore_settings(self, global_settings, perspective_settings):
00451 qDebug('PluginManager.restore_settings()')
00452 self._global_settings = global_settings.get_settings('pluginmanager')
00453 self._perspective_settings = perspective_settings.get_settings('pluginmanager')
00454 self._restore_settings_save_obsolete()
00455
00456 def _restore_settings_save_obsolete(self):
00457
00458 plugins = self._restore_running_plugins_get_plugins()
00459 obsolete = []
00460 for instance_id in self._running_plugins.keys():
00461 if instance_id not in plugins.keys():
00462 obsolete.append(PluginInstanceId(instance_id=instance_id))
00463 self._number_of_ongoing_calls = len(obsolete)
00464 if self._number_of_ongoing_calls > 0:
00465 qDebug('PluginManager.restore_settings() unloading %d obsolete plugins' % self._number_of_ongoing_calls)
00466 for instance_id in obsolete:
00467 self._shutdown_plugin(instance_id, self._restore_settings_unload_obsolete)
00468 else:
00469 self._restore_settings_unload_obsolete_callback()
00470
00471 def _restore_running_plugins_get_plugins(self):
00472 plugins = {}
00473 if self._perspective_settings is not None:
00474 data = self._perspective_settings.value('running-plugins', {})
00475 for plugin_id, serial_numbers in data.items():
00476 for serial_number in serial_numbers:
00477 instance_id = PluginInstanceId(plugin_id, serial_number)
00478 plugins[str(instance_id)] = instance_id
00479 return plugins
00480
00481 def _restore_settings_unload_obsolete(self, instance_id):
00482
00483 self._unload_plugin(instance_id, self._restore_settings_unload_obsolete_callback)
00484
00485 def _restore_settings_unload_obsolete_callback(self, instance_id=None):
00486 if instance_id is not None:
00487 self._number_of_ongoing_calls = self._number_of_ongoing_calls - 1
00488 self._remove_running_plugin(instance_id)
00489 if self._number_of_ongoing_calls == 0:
00490 if instance_id is not None:
00491 qDebug('PluginManager.restore_settings() all obsolete plugins unloaded')
00492 self._number_of_ongoing_calls = None
00493 self._restore_settings_load_missing()
00494
00495 def _restore_settings_load_missing(self):
00496
00497 plugins = self._restore_running_plugins_get_plugins()
00498 loading = []
00499 for instance_id_str, instance_id in plugins.items():
00500 if instance_id_str not in self._running_plugins.keys():
00501 loading.append(instance_id)
00502 self._number_of_ongoing_calls = len(loading)
00503 if self._number_of_ongoing_calls > 0:
00504 qDebug('PluginManager.restore_settings() loading %d plugins' % self._number_of_ongoing_calls)
00505 for instance_id in loading:
00506 self._load_plugin_load(instance_id, self._restore_settings_load_missing_callback)
00507 else:
00508 self._restore_settings_load_missing_callback()
00509
00510 def _restore_settings_load_missing_callback(self, handler=None, exception=None):
00511 if handler is not None:
00512 self._number_of_ongoing_calls = self._number_of_ongoing_calls - 1
00513 self._load_plugin_completed(handler, exception)
00514 if self._number_of_ongoing_calls == 0:
00515 if handler is not None:
00516 qDebug('PluginManager.restore_settings() all missing plugins loaded')
00517 self._number_of_ongoing_calls = None
00518 self._restore_settings_restore()
00519
00520 def restore_settings_without_plugins(self, global_settings, perspective_settings):
00521 qDebug('PluginManager.restore_settings_without_plugins()')
00522 self._global_settings = global_settings.get_settings('pluginmanager')
00523 self._perspective_settings = perspective_settings.get_settings('pluginmanager')
00524 self._restore_settings_restore()
00525
00526 def _restore_settings_restore(self):
00527
00528 self._number_of_ongoing_calls = len(self._running_plugins)
00529 if self._number_of_ongoing_calls > 0:
00530 for info in self._running_plugins.values():
00531 self._restore_plugin_settings(info['instance_id'], self._restore_settings_restore_callback)
00532 else:
00533 self._restore_settings_restore_callback()
00534
00535 def _restore_settings_restore_callback(self, instance_id=None):
00536 if instance_id is not None:
00537 self._number_of_ongoing_calls = self._number_of_ongoing_calls - 1
00538 if self._number_of_ongoing_calls == 0:
00539 if instance_id is not None:
00540 qDebug('PluginManager.restore_settings() all plugin settings restored')
00541 self._number_of_ongoing_calls = None
00542
00543 self.plugins_changed_signal.emit()