From 19ff9ac664bdc7afc99f6cc47f029be094ae3418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 23 Jun 2016 13:54:14 +0200 Subject: [PATCH] Allow plugins to define settings overlays --- src/octoprint/__init__.py | 59 +++++++++++++++++++++++++++++++- src/octoprint/plugin/core.py | 24 +++++++++++-- src/octoprint/server/__init__.py | 19 ++++++---- src/octoprint/settings.py | 14 ++++++-- 4 files changed, 104 insertions(+), 12 deletions(-) diff --git a/src/octoprint/__init__.py b/src/octoprint/__init__.py index fa67ad1c..4c20bd9f 100644 --- a/src/octoprint/__init__.py +++ b/src/octoprint/__init__.py @@ -201,8 +201,65 @@ def init_logging(settings, use_logging_file=True, logging_file=None, default_con def init_pluginsystem(settings): """Initializes the plugin manager based on the settings.""" + from octoprint.plugin import plugin_manager - return plugin_manager(init=True, settings=settings) + pm = plugin_manager(init=True, settings=settings) + + logger = logging.getLogger(__name__) + settings_overlays = dict() + disabled_from_overlays = dict() + + def handle_plugin_loaded(name, plugin): + if hasattr(plugin.instance, "__plugin_settings_overlay__"): + # plugin has a settings overlay, inject it + overlay_definition = getattr(plugin.instance, "__plugin_settings_overlay__") + if isinstance(overlay_definition, (tuple, list)): + overlay_definition, order = overlay_definition + else: + order = None + + overlay = settings.load_overlay(overlay_definition) + + if "plugins" in overlay and "_disabled" in overlay["plugins"]: + disabled_plugins = overlay["plugins"]["_disabled"] + del overlay["plugins"]["_disabled"] + disabled_from_overlays[name] = (disabled_plugins, order) + + settings_overlays[name] = overlay + logger.debug("Found settings overlay on plugin {}".format(name)) + + def handle_plugins_loaded(startup=False, initialize_implementations=True, force_reload=None): + if not startup: + return + + sorted_disabled_from_overlays = sorted([(key, value[0], value[1]) for key, value in disabled_from_overlays.items()], key=lambda x: (x[2] is None, x[2], x[0])) + + disabled_list = pm.plugin_disabled_list + already_processed = [] + for name, addons, _ in sorted_disabled_from_overlays: + if not name in disabled_list and not name.endswith("disabled"): + for addon in addons: + if addon in disabled_list: + continue + + if addon in already_processed: + logger.info("Plugin {} wants to disable plugin {}, but that was already processed".format(name, addon)) + + if not addon in already_processed and not addon in disabled_list: + disabled_list.append(addon) + logger.info("Disabling plugin {} as defined by plugin {} through settings overlay".format(addon, name)) + already_processed.append(name) + + def handle_plugin_enabled(name, plugin): + if name in settings_overlays: + settings.add_overlay(settings_overlays[name]) + logger.info("Added settings overlay from plugin {}".format(name)) + + pm.on_plugin_loaded = handle_plugin_loaded + pm.on_plugins_loaded = handle_plugins_loaded + pm.on_plugin_enabled = handle_plugin_enabled + pm.reload_plugins(startup=True, initialize_implementations=False) + return pm #~~ server main method diff --git a/src/octoprint/plugin/core.py b/src/octoprint/plugin/core.py index 869d15df..cbcf51e2 100644 --- a/src/octoprint/plugin/core.py +++ b/src/octoprint/plugin/core.py @@ -472,6 +472,9 @@ class PluginManager(object): self.on_plugin_disabled = lambda *args, **kwargs: None self.on_plugin_implementations_initialized = lambda *args, **kwargs: None + self.on_plugins_loaded = lambda *args, **kwargs: None + self.on_plugins_enabled = lambda *args, **kwargs: None + self.registered_clients = [] self.marked_plugins = defaultdict(list) @@ -480,8 +483,6 @@ class PluginManager(object): self._python_virtual_env = False self._detect_python_environment() - self.reload_plugins(startup=True, initialize_implementations=False) - def _detect_python_environment(self): from distutils.command.install import install as cmd_install from distutils.dist import Distribution @@ -667,16 +668,33 @@ class PluginManager(object): plugins = self.find_plugins(existing=dict((k, v) for k, v in self.plugins.items() if not k in force_reload)) self.disabled_plugins.update(plugins) + # 1st pass: loading the plugins for name, plugin in plugins.items(): try: self.load_plugin(name, plugin, startup=startup, initialize_implementation=initialize_implementations) - if not self._is_plugin_disabled(name): + except PluginNeedsRestart: + pass + except PluginLifecycleException as e: + self.logger.info(str(e)) + + self.on_plugins_loaded(startup=startup, + initialize_implementations=initialize_implementations, + force_reload=force_reload) + + # 2nd pass: enabling those plugins that need enabling + for name, plugin in plugins.items(): + try: + if plugin.loaded and not self._is_plugin_disabled(name): self.enable_plugin(name, plugin=plugin, initialize_implementation=initialize_implementations, startup=startup) except PluginNeedsRestart: pass except PluginLifecycleException as e: self.logger.info(str(e)) + self.on_plugins_enabled(startup=startup, + initialize_implementations=initialize_implementations, + force_reload=force_reload) + if len(self.enabled_plugins) <= 0: self.logger.info("No plugins found") else: diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 37c631e4..a82a85a8 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -1165,17 +1165,24 @@ class LifecycleManager(object): self._plugin_lifecycle_callbacks = defaultdict(list) self._logger = logging.getLogger(__name__) + def wrap_plugin_event(lifecycle_event, new_handler): + orig_handler = getattr(self._plugin_manager, "on_plugin_" + lifecycle_event) + + def handler(*args, **kwargs): + if callable(orig_handler): + orig_handler(*args, **kwargs) + if callable(new_handler): + new_handler(*args, **kwargs) + + return handler + def on_plugin_event_factory(lifecycle_event): def on_plugin_event(name, plugin): self.on_plugin_event(lifecycle_event, name, plugin) return on_plugin_event - self._plugin_manager.on_plugin_loaded = on_plugin_event_factory("loaded") - self._plugin_manager.on_plugin_unloaded = on_plugin_event_factory("unloaded") - self._plugin_manager.on_plugin_activated = on_plugin_event_factory("activated") - self._plugin_manager.on_plugin_deactivated = on_plugin_event_factory("deactivated") - self._plugin_manager.on_plugin_enabled = on_plugin_event_factory("enabled") - self._plugin_manager.on_plugin_disabled = on_plugin_event_factory("disabled") + for event in ("loaded", "unloaded", "enabled", "disabled"): + wrap_plugin_event(event, on_plugin_event_factory(event)) def on_plugin_event(self, event, name, plugin): for lifecycle_callback in self._plugin_lifecycle_callbacks[event]: diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index e7968ce2..148ac28b 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -720,9 +720,16 @@ class Settings(object): if migrate: self._migrate_config() - def add_overlay(self, overlay, migrate=False): + def load_overlay(self, overlay, migrate=True): config = None + if callable(overlay): + try: + overlay = overlay() + except: + self._logger.exception("Error loading overlay from callable") + return + if isinstance(overlay, basestring): if os.path.exists(overlay) and os.path.isfile(overlay): with open(overlay, "r") as f: @@ -737,7 +744,10 @@ class Settings(object): if migrate: self._migrate_config(config) - self._map.parents.maps.insert(0, config) + return config + + def add_overlay(self, overlay): + self._map.maps.insert(1, overlay) def _migrate_config(self, config=None): if config is None: