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