Allow plugins to define settings overlays

This commit is contained in:
Gina Häußge 2016-06-23 13:54:14 +02:00
parent c879ae4099
commit 19ff9ac664
4 changed files with 104 additions and 12 deletions

View file

@ -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

View file

@ -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:

View file

@ -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]:

View file

@ -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: