First throw at working plugin lifecycle management
Plugins may be enabled and disabled during runtime. If they are of types which allow hot loading, this will be done. Otherwise they will be marked as pending and updated after a restart. Same for installation and uninstallation.
This commit is contained in:
parent
f0b48a6b43
commit
97aecdf4cf
6 changed files with 371 additions and 168 deletions
|
|
@ -88,11 +88,7 @@ def plugin_manager(init=False, plugin_folders=None, plugin_types=None, plugin_en
|
||||||
if plugin_entry_points is None:
|
if plugin_entry_points is None:
|
||||||
plugin_entry_points = "octoprint.plugin"
|
plugin_entry_points = "octoprint.plugin"
|
||||||
if plugin_disabled_list is None:
|
if plugin_disabled_list is None:
|
||||||
all_plugin_settings = settings().get(["plugins"])
|
plugin_disabled_list = settings().get(["plugins", "_disabled"])
|
||||||
plugin_disabled_list = []
|
|
||||||
for key in all_plugin_settings:
|
|
||||||
if "enabled" in all_plugin_settings[key] and not all_plugin_settings[key]:
|
|
||||||
plugin_disabled_list.append(key)
|
|
||||||
|
|
||||||
_instance = PluginManager(plugin_folders, plugin_types, plugin_entry_points, logging_prefix="octoprint.plugins.", plugin_disabled_list=plugin_disabled_list)
|
_instance = PluginManager(plugin_folders, plugin_types, plugin_entry_points, logging_prefix="octoprint.plugins.", plugin_disabled_list=plugin_disabled_list)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import imp
|
import imp
|
||||||
from collections import defaultdict
|
from collections import defaultdict, namedtuple
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -113,6 +113,7 @@ class PluginInfo(object):
|
||||||
self.origin = None
|
self.origin = None
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
self.bundled = False
|
self.bundled = False
|
||||||
|
self.loaded = False
|
||||||
|
|
||||||
self._name = name
|
self._name = name
|
||||||
self._version = version
|
self._version = version
|
||||||
|
|
@ -385,26 +386,34 @@ class PluginManager(object):
|
||||||
|
|
||||||
self.on_plugin_loaded = lambda *args, **kwargs: None
|
self.on_plugin_loaded = lambda *args, **kwargs: None
|
||||||
self.on_plugin_unloaded = lambda *args, **kwargs: None
|
self.on_plugin_unloaded = lambda *args, **kwargs: None
|
||||||
self.on_plugin_activated = lambda *args, **kwargs: None
|
|
||||||
self.on_plugin_deactivated = lambda *args, **kwargs: None
|
|
||||||
self.on_plugin_enabled = lambda *args, **kwargs: None
|
self.on_plugin_enabled = lambda *args, **kwargs: None
|
||||||
self.on_plugin_disabled = lambda *args, **kwargs: None
|
self.on_plugin_disabled = lambda *args, **kwargs: None
|
||||||
self.on_plugin_implementations_initialized = lambda *args, **kwargs: None
|
self.on_plugin_implementations_initialized = lambda *args, **kwargs: None
|
||||||
|
|
||||||
self.registered_clients = []
|
self.registered_clients = []
|
||||||
|
|
||||||
self.reload_plugins()
|
self.reload_plugins(startup=True, initialize_implementations=False)
|
||||||
|
|
||||||
def _find_plugins(self):
|
@property
|
||||||
plugins = dict()
|
def all_plugins(self):
|
||||||
disabled_plugins = dict()
|
plugins = dict(self.plugins)
|
||||||
|
plugins.update(self.disabled_plugins)
|
||||||
|
return plugins
|
||||||
|
|
||||||
|
def find_plugins(self, existing=None):
|
||||||
|
if existing is None:
|
||||||
|
existing = self.all_plugins
|
||||||
|
|
||||||
|
result = dict()
|
||||||
if self.plugin_folders:
|
if self.plugin_folders:
|
||||||
self._add_plugins_from_folders(self.plugin_folders, plugins, disabled_plugins)
|
result.update(self._find_plugins_from_folders(self.plugin_folders, existing))
|
||||||
if self.plugin_entry_points:
|
if self.plugin_entry_points:
|
||||||
self._add_plugins_from_entry_points(self.plugin_entry_points, plugins, disabled_plugins)
|
result.update(self._find_plugins_from_entry_points(self.plugin_entry_points, existing))
|
||||||
return plugins, disabled_plugins
|
return result
|
||||||
|
|
||||||
|
def _find_plugins_from_folders(self, folders, existing):
|
||||||
|
result = dict()
|
||||||
|
|
||||||
def _add_plugins_from_folders(self, folders, plugins, disabled_plugins):
|
|
||||||
for folder in folders:
|
for folder in folders:
|
||||||
readonly = False
|
readonly = False
|
||||||
if isinstance(folder, (list, tuple)):
|
if isinstance(folder, (list, tuple)):
|
||||||
|
|
@ -427,7 +436,7 @@ class PluginManager(object):
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if key in plugins:
|
if key in existing or key in result:
|
||||||
# plugin is already defined, ignore it
|
# plugin is already defined, ignore it
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -437,28 +446,31 @@ class PluginManager(object):
|
||||||
if readonly:
|
if readonly:
|
||||||
plugin.bundled = True
|
plugin.bundled = True
|
||||||
|
|
||||||
if self._is_plugin_disabled(key):
|
plugin.enabled = False
|
||||||
plugin.enabled = False
|
|
||||||
disabled_plugins[key] = plugin
|
|
||||||
else:
|
|
||||||
plugins[key] = plugin
|
|
||||||
|
|
||||||
return plugins, disabled_plugins
|
result[key] = plugin
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _find_plugins_from_entry_points(self, groups, existing):
|
||||||
|
result = dict()
|
||||||
|
|
||||||
def _add_plugins_from_entry_points(self, groups, plugins, disabled_plugins):
|
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
import pkginfo
|
import pkginfo
|
||||||
|
|
||||||
|
# let's make sure we have a current working set
|
||||||
|
working_set = pkg_resources.WorkingSet()
|
||||||
|
|
||||||
if not isinstance(groups, (list, tuple)):
|
if not isinstance(groups, (list, tuple)):
|
||||||
groups = [groups]
|
groups = [groups]
|
||||||
|
|
||||||
for group in groups:
|
for group in groups:
|
||||||
for entry_point in pkg_resources.iter_entry_points(group=group, name=None):
|
for entry_point in working_set.iter_entry_points(group=group, name=None):
|
||||||
key = entry_point.name
|
key = entry_point.name
|
||||||
module_name = entry_point.module_name
|
module_name = entry_point.module_name
|
||||||
version = entry_point.dist.version
|
version = entry_point.dist.version
|
||||||
|
|
||||||
if key in plugins:
|
if key in existing or key in result:
|
||||||
# plugin is already defined, ignore it
|
# plugin is already defined, ignore it
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -479,14 +491,10 @@ class PluginManager(object):
|
||||||
plugin = self._import_plugin_from_module(key, **kwargs)
|
plugin = self._import_plugin_from_module(key, **kwargs)
|
||||||
if plugin:
|
if plugin:
|
||||||
plugin.origin = ("entry_point", group, module_name)
|
plugin.origin = ("entry_point", group, module_name)
|
||||||
|
plugin.enabled = False
|
||||||
|
result[key] = plugin
|
||||||
|
|
||||||
if self._is_plugin_disabled(key):
|
return result
|
||||||
plugin.enabled = False
|
|
||||||
disabled_plugins[key] = plugin
|
|
||||||
else:
|
|
||||||
plugins[key] = plugin
|
|
||||||
|
|
||||||
return plugins, disabled_plugins
|
|
||||||
|
|
||||||
def _import_plugin_from_module(self, key, folder=None, module_name=None, name=None, version=None, summary=None, author=None, url=None, license=None):
|
def _import_plugin_from_module(self, key, folder=None, module_name=None, name=None, version=None, summary=None, author=None, url=None, license=None):
|
||||||
# TODO error handling
|
# TODO error handling
|
||||||
|
|
@ -502,12 +510,14 @@ class PluginManager(object):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
plugin = self._import_plugin(key, *module, name=name, version=version, summary=summary, author=author, url=url, license=license)
|
plugin = self._import_plugin(key, *module, name=name, version=version, summary=summary, author=author, url=url, license=license)
|
||||||
if plugin:
|
if plugin is None:
|
||||||
if plugin.check():
|
return None
|
||||||
return plugin
|
|
||||||
else:
|
if plugin.check():
|
||||||
self.logger.warn("Plugin \"{plugin}\" did not pass check".format(plugin=str(plugin)))
|
return plugin
|
||||||
return None
|
else:
|
||||||
|
self.logger.warn("Plugin \"{plugin}\" did not pass check".format(plugin=str(plugin)))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _import_plugin(self, key, f, filename, description, name=None, version=None, summary=None, author=None, url=None, license=None):
|
def _import_plugin(self, key, f, filename, description, name=None, version=None, summary=None, author=None, url=None, license=None):
|
||||||
|
|
@ -515,22 +525,27 @@ class PluginManager(object):
|
||||||
instance = imp.load_module(key, f, filename, description)
|
instance = imp.load_module(key, f, filename, description)
|
||||||
return PluginInfo(key, filename, instance, name=name, version=version, description=summary, author=author, url=url, license=license)
|
return PluginInfo(key, filename, instance, name=name, version=version, description=summary, author=author, url=url, license=license)
|
||||||
except:
|
except:
|
||||||
self.logger.exception("Error loading plugin {key}, disabling it".format(key=key))
|
self.logger.exception("Error loading plugin {key}".format(key=key))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _is_plugin_disabled(self, key):
|
def _is_plugin_disabled(self, key):
|
||||||
return key in self.plugin_disabled_list or key.endswith('disabled')
|
return key in self.plugin_disabled_list or key.endswith('disabled')
|
||||||
|
|
||||||
def reload_plugins(self):
|
def reload_plugins(self, startup=False, initialize_implementations=True):
|
||||||
self.logger.info("Loading plugins from {folders} and installed plugin packages...".format(
|
self.logger.info("Loading plugins from {folders} and installed plugin packages...".format(
|
||||||
folders=", ".join(map(lambda x: x[0] if isinstance(x, tuple) else str(x), self.plugin_folders))
|
folders=", ".join(map(lambda x: x[0] if isinstance(x, tuple) else str(x), self.plugin_folders))
|
||||||
))
|
))
|
||||||
plugins, disabled_plugins = self._find_plugins()
|
|
||||||
|
|
||||||
self.disabled_plugins = disabled_plugins
|
plugins = self.find_plugins()
|
||||||
|
self.disabled_plugins.update(plugins)
|
||||||
|
|
||||||
for name, plugin in plugins.items():
|
for name, plugin in plugins.items():
|
||||||
self.load_plugin(name, plugin)
|
try:
|
||||||
|
self.load_plugin(name, plugin, startup=startup, initialize_implementation=initialize_implementations)
|
||||||
|
if not self._is_plugin_disabled(name):
|
||||||
|
self.enable_plugin(name, plugin=plugin, initialize_implementation=initialize_implementations, startup=startup)
|
||||||
|
except (PluginLifecycleException, PluginNeedsRestart):
|
||||||
|
pass
|
||||||
|
|
||||||
if len(self.plugins) <= 0:
|
if len(self.plugins) <= 0:
|
||||||
self.logger.info("No plugins found")
|
self.logger.info("No plugins found")
|
||||||
|
|
@ -541,111 +556,158 @@ class PluginManager(object):
|
||||||
hooks=sum(map(lambda x: len(x), self.plugin_hooks.values()))
|
hooks=sum(map(lambda x: len(x), self.plugin_hooks.values()))
|
||||||
))
|
))
|
||||||
|
|
||||||
@property
|
def load_plugin(self, name, plugin=None, startup=False, initialize_implementation=True):
|
||||||
def all_plugins(self):
|
if not name in self.all_plugins:
|
||||||
plugins = dict(self.plugins)
|
self.logger.warn("Trying to load an unknown plugin {name}".format(**locals()))
|
||||||
plugins.update(self.disabled_plugins)
|
return
|
||||||
return plugins
|
|
||||||
|
if plugin is None:
|
||||||
|
plugin = self.all_plugins[name]
|
||||||
|
|
||||||
def load_plugin(self, name, plugin):
|
|
||||||
try:
|
try:
|
||||||
plugin.load()
|
plugin.load()
|
||||||
plugin.validate()
|
plugin.validate()
|
||||||
self._activate_plugin(name, plugin)
|
self.on_plugin_loaded(name, plugin)
|
||||||
|
|
||||||
|
plugin.loaded = True
|
||||||
|
|
||||||
|
self.logger.debug("Loaded plugin {name}: {plugin}".format(**locals()))
|
||||||
|
except PluginLifecycleException as e:
|
||||||
|
raise e
|
||||||
except:
|
except:
|
||||||
self.logger.exception("There was an error loading plugin %s" % name)
|
self.logger.exception("There was an error loading plugin %s" % name)
|
||||||
self.disabled_plugins[name] = plugin
|
|
||||||
else:
|
|
||||||
self.plugins[name] = plugin
|
|
||||||
self.on_plugin_loaded(name, plugin)
|
|
||||||
self.logger.debug("Loaded plugin {name}: {plugin}".format(**locals()))
|
|
||||||
|
|
||||||
def unload_plugin(self, name):
|
def unload_plugin(self, name):
|
||||||
if not name in self.plugins:
|
if not name in self.all_plugins:
|
||||||
if name in self.disabled_plugins:
|
self.logger.warn("Trying to unload unknown plugin {name}".format(**locals()))
|
||||||
plugin = self.disabled_plugins[name]
|
return
|
||||||
else:
|
|
||||||
self.logger.warn("Trying to unload unknown plugin {name}".format(**locals()))
|
plugin = self.all_plugins[name]
|
||||||
return
|
|
||||||
else:
|
|
||||||
plugin = self.plugins[name]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._deactivate_plugin(name, plugin)
|
if plugin.enabled:
|
||||||
|
self.disable_plugin(name, plugin=plugin)
|
||||||
|
|
||||||
plugin.unload()
|
plugin.unload()
|
||||||
|
self.on_plugin_unloaded(name, plugin)
|
||||||
|
|
||||||
if name in self.plugins:
|
if name in self.plugins:
|
||||||
del self.plugins[name]
|
del self.plugins[name]
|
||||||
|
|
||||||
if name in self.disabled_plugins:
|
if name in self.disabled_plugins:
|
||||||
del self.disabled_plugins[name]
|
del self.disabled_plugins[name]
|
||||||
|
|
||||||
|
plugin.loaded = False
|
||||||
|
|
||||||
|
self.logger.debug("Unloaded plugin {name}: {plugin}".format(**locals()))
|
||||||
|
except PluginLifecycleException as e:
|
||||||
|
raise e
|
||||||
except:
|
except:
|
||||||
self.logger.exception("There was an error unloading plugin {name}".format(**locals()))
|
self.logger.exception("There was an error unloading plugin {name}".format(**locals()))
|
||||||
else:
|
|
||||||
self.on_plugin_unloaded(name, plugin)
|
|
||||||
self.logger.debug("Unloaded plugin {name}: {plugin}".format(**locals()))
|
|
||||||
|
|
||||||
def enable_plugin(self, name):
|
# make sure the plugin is NOT in the list of enabled plugins but in the list of disabled plugins
|
||||||
|
if name in self.plugins:
|
||||||
|
del self.plugins[name]
|
||||||
|
if not name in self.disabled_plugins:
|
||||||
|
self.disabled_plugins[name] = plugin
|
||||||
|
|
||||||
|
def enable_plugin(self, name, plugin=None, initialize_implementation=True, startup=False):
|
||||||
if not name in self.disabled_plugins:
|
if not name in self.disabled_plugins:
|
||||||
self.logger.warn("Tried to enable plugin {name}, however it is not disabled".format(**locals()))
|
self.logger.warn("Tried to enable plugin {name}, however it is not disabled".format(**locals()))
|
||||||
return
|
return
|
||||||
|
|
||||||
plugin = self.disabled_plugins[name]
|
if plugin is None:
|
||||||
|
plugin = self.disabled_plugins[name]
|
||||||
|
|
||||||
|
if not startup and plugin.implementation and isinstance(plugin.implementation, RestartNeedingPlugin):
|
||||||
|
raise PluginNeedsRestart(name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
del self.disabled_plugins[name]
|
|
||||||
plugin.enable()
|
plugin.enable()
|
||||||
self._activate_plugin(name, plugin)
|
self._activate_plugin(name, plugin)
|
||||||
self.initialize_implementation_of_plugin(name, plugin)
|
except PluginLifecycleException as e:
|
||||||
self.plugins[name] = plugin
|
raise e
|
||||||
except:
|
except:
|
||||||
self.logger.exception("There was an error while enabling plugin {name}".format(**locals()))
|
self.logger.exception("There was an error while enabling plugin {name}".format(**locals()))
|
||||||
self.disabled_plugins[name] = plugin
|
return False
|
||||||
else:
|
else:
|
||||||
|
if name in self.disabled_plugins:
|
||||||
|
del self.disabled_plugins[name]
|
||||||
|
self.plugins[name] = plugin
|
||||||
|
plugin.enabled = True
|
||||||
|
|
||||||
|
if plugin.implementation:
|
||||||
|
if initialize_implementation:
|
||||||
|
if not self.initialize_implementation_of_plugin(name, plugin):
|
||||||
|
return False
|
||||||
|
plugin.implementation.on_plugin_enabled()
|
||||||
self.on_plugin_enabled(name, plugin)
|
self.on_plugin_enabled(name, plugin)
|
||||||
|
|
||||||
self.logger.debug("Enabled plugin {name}: {plugin}".format(**locals()))
|
self.logger.debug("Enabled plugin {name}: {plugin}".format(**locals()))
|
||||||
|
|
||||||
def disable_plugin(self, name):
|
return True
|
||||||
|
|
||||||
|
def disable_plugin(self, name, plugin=None):
|
||||||
if not name in self.plugins:
|
if not name in self.plugins:
|
||||||
self.logger.warn("Tried to disable plugin {name}, however it is not enabled".format(**locals()))
|
self.logger.warn("Tried to disable plugin {name}, however it is not enabled".format(**locals()))
|
||||||
return
|
return
|
||||||
|
|
||||||
plugin = self.plugins[name]
|
if plugin is None:
|
||||||
|
plugin = self.plugins[name]
|
||||||
|
|
||||||
|
if plugin.implementation and isinstance(plugin.implementation, RestartNeedingPlugin):
|
||||||
|
raise PluginNeedsRestart(name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
del self.plugins[name]
|
|
||||||
plugin.disable()
|
plugin.disable()
|
||||||
self._deactivate_plugin(name, plugin)
|
self._deactivate_plugin(name, plugin)
|
||||||
self.disabled_plugins[name] = plugin
|
except PluginLifecycleException as e:
|
||||||
|
raise e
|
||||||
except:
|
except:
|
||||||
self.logger.exception("There was an error while disabling plugin {name}".format(**locals()))
|
self.logger.exception("There was an error while disabling plugin {name}".format(**locals()))
|
||||||
self.plugins[name] = plugin
|
return False
|
||||||
else:
|
else:
|
||||||
|
if name in self.plugins:
|
||||||
|
del self.plugins[name]
|
||||||
|
self.disabled_plugins[name] = plugin
|
||||||
|
plugin.enabled = False
|
||||||
|
|
||||||
|
if plugin.implementation:
|
||||||
|
plugin.implementation.on_plugin_disabled()
|
||||||
self.on_plugin_disabled(name, plugin)
|
self.on_plugin_disabled(name, plugin)
|
||||||
|
|
||||||
self.logger.debug("Disabled plugin {name}: {plugin}".format(**locals()))
|
self.logger.debug("Disabled plugin {name}: {plugin}".format(**locals()))
|
||||||
|
|
||||||
def _activate_plugin(self, name, plugin):
|
return True
|
||||||
plugin.activate()
|
|
||||||
|
|
||||||
|
def _activate_plugin(self, name, plugin):
|
||||||
# evaluate registered hooks
|
# evaluate registered hooks
|
||||||
for hook, callback in plugin.hooks.items():
|
for hook, callback in plugin.hooks.items():
|
||||||
self.plugin_hooks[hook].append((name, callback))
|
self.plugin_hooks[hook].append((name, callback))
|
||||||
|
|
||||||
# evaluate registered implementation
|
# evaluate registered implementation
|
||||||
if plugin.implementation:
|
if plugin.implementation:
|
||||||
|
if isinstance(plugin.implementation, RestartNeedingPlugin):
|
||||||
|
plugin.hotchangeable = False
|
||||||
|
|
||||||
for plugin_type in self.plugin_types:
|
for plugin_type in self.plugin_types:
|
||||||
if isinstance(plugin.implementation, plugin_type):
|
if isinstance(plugin.implementation, plugin_type):
|
||||||
self.plugin_implementations_by_type[plugin_type].append((name, plugin.implementation))
|
self.plugin_implementations_by_type[plugin_type].append((name, plugin.implementation))
|
||||||
|
|
||||||
self.plugin_implementations[name] = plugin.implementation
|
self.plugin_implementations[name] = plugin.implementation
|
||||||
|
|
||||||
self.on_plugin_activated(name, plugin)
|
|
||||||
|
|
||||||
def _deactivate_plugin(self, name, plugin):
|
def _deactivate_plugin(self, name, plugin):
|
||||||
plugin.deactivate()
|
|
||||||
for hook, callback in plugin.hooks.items():
|
for hook, callback in plugin.hooks.items():
|
||||||
self.plugin_hooks[hook].remove((name, callback))
|
try:
|
||||||
|
self.plugin_hooks[hook].remove((name, callback))
|
||||||
|
except ValueError:
|
||||||
|
# that's ok, the plugin was just not registered for the hook
|
||||||
|
pass
|
||||||
|
|
||||||
if plugin.implementation is not None:
|
if plugin.implementation is not None:
|
||||||
del self.plugin_implementations[name]
|
if name in self.plugin_implementations:
|
||||||
|
del self.plugin_implementations[name]
|
||||||
|
|
||||||
for plugin_type in self.plugin_types:
|
for plugin_type in self.plugin_types:
|
||||||
try:
|
try:
|
||||||
self.plugin_implementations_by_type[plugin_type].remove((name, plugin.implementation))
|
self.plugin_implementations_by_type[plugin_type].remove((name, plugin.implementation))
|
||||||
|
|
@ -653,8 +715,6 @@ class PluginManager(object):
|
||||||
# that's ok, the plugin was just not registered for the type
|
# that's ok, the plugin was just not registered for the type
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.on_plugin_deactivated(name, plugin)
|
|
||||||
|
|
||||||
def initialize_implementations(self, additional_injects=None, additional_inject_factories=None):
|
def initialize_implementations(self, additional_injects=None, additional_inject_factories=None):
|
||||||
for name, plugin in self.plugins.items():
|
for name, plugin in self.plugins.items():
|
||||||
self.initialize_implementation_of_plugin(name, plugin,
|
self.initialize_implementation_of_plugin(name, plugin,
|
||||||
|
|
@ -667,7 +727,7 @@ class PluginManager(object):
|
||||||
if plugin.implementation is None:
|
if plugin.implementation is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.initialize_implementation(name, plugin, plugin.implementation,
|
return self.initialize_implementation(name, plugin, plugin.implementation,
|
||||||
additional_injects=additional_injects,
|
additional_injects=additional_injects,
|
||||||
additional_inject_factories=additional_inject_factories)
|
additional_inject_factories=additional_inject_factories)
|
||||||
|
|
||||||
|
|
@ -710,18 +770,22 @@ class PluginManager(object):
|
||||||
for arg, value in return_value.items():
|
for arg, value in return_value.items():
|
||||||
setattr(implementation, "_" + arg, value)
|
setattr(implementation, "_" + arg, value)
|
||||||
|
|
||||||
result = implementation.initialize()
|
implementation.initialize()
|
||||||
if result is not None and not result:
|
|
||||||
self.logger.warn("Initialization of {name} returned False, disabling it".format(**locals()))
|
except Exception as e:
|
||||||
self._deactivate_plugin(name, plugin)
|
|
||||||
return
|
|
||||||
except:
|
|
||||||
self.logger.exception("Exception while initializing plugin {name}, disabling it".format(**locals()))
|
|
||||||
self._deactivate_plugin(name, plugin)
|
self._deactivate_plugin(name, plugin)
|
||||||
|
plugin.enabled = False
|
||||||
|
|
||||||
|
if isinstance(e, PluginLifecycleException):
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
self.logger.exception("Exception while initializing plugin {name}, disabling it".format(**locals()))
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
self.on_plugin_implementations_initialized(name, plugin)
|
self.on_plugin_implementations_initialized(name, plugin)
|
||||||
|
|
||||||
self.logger.debug("Initialized plugin mixin implementation for plugin {name}".format(**locals()))
|
self.logger.debug("Initialized plugin mixin implementation for plugin {name}".format(**locals()))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def log_all_plugins(self, show_bundled=True, bundled_str=(" (bundled)", ""), show_location=True, location_str=" = {location}", show_enabled=True, enabled_str=(" ", "!")):
|
def log_all_plugins(self, show_bundled=True, bundled_str=(" (bundled)", ""), show_location=True, location_str=" = {location}", show_enabled=True, enabled_str=(" ", "!")):
|
||||||
|
|
@ -927,3 +991,38 @@ class Plugin(object):
|
||||||
Called by the plugin core after performing all injections. Override this to initialize your implementation.
|
Called by the plugin core after performing all injections. Override this to initialize your implementation.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def on_plugin_enabled(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_plugin_disabled(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class RestartNeedingPlugin(Plugin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PluginNeedsRestart(BaseException):
|
||||||
|
def __init__(self, name):
|
||||||
|
super(BaseException, self).__init__()
|
||||||
|
self.name = name
|
||||||
|
self.message = "Plugin {name} cannot be enabled or disabled after system startup".format(**locals())
|
||||||
|
|
||||||
|
class PluginLifecycleException(BaseException):
|
||||||
|
def __init__(self, name, reason, message):
|
||||||
|
super(BaseException, self).__init__()
|
||||||
|
self.name = name
|
||||||
|
self.reason = reason
|
||||||
|
|
||||||
|
self.message = message.format(**locals())
|
||||||
|
|
||||||
|
class PluginCantInitialize(PluginLifecycleException):
|
||||||
|
def __init__(self, name, reason):
|
||||||
|
super(PluginLifecycleException, self).__init__(name, reason, "Plugin {name} cannot be initialized: {message}")
|
||||||
|
|
||||||
|
class PluginCantEnable(PluginLifecycleException):
|
||||||
|
def __init__(self, name, reason):
|
||||||
|
super(PluginLifecycleException, self).__init__(name, reason, "Plugin {name} cannot be enabled: {message}")
|
||||||
|
|
||||||
|
class PluginCantDisable(PluginLifecycleException):
|
||||||
|
def __init__(self, name, reason):
|
||||||
|
super(PluginLifecycleException, self).__init__(name, reason, "Plugin {name} cannot be disabled: {message}")
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agp
|
||||||
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
|
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
|
||||||
|
|
||||||
|
|
||||||
from .core import Plugin
|
from .core import (Plugin, RestartNeedingPlugin)
|
||||||
|
|
||||||
|
|
||||||
class OctoPrintPlugin(Plugin):
|
class OctoPrintPlugin(Plugin):
|
||||||
|
|
@ -68,6 +68,8 @@ class OctoPrintPlugin(Plugin):
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class ReloadNeedingPlugin(Plugin):
|
||||||
|
pass
|
||||||
|
|
||||||
class StartupPlugin(OctoPrintPlugin):
|
class StartupPlugin(OctoPrintPlugin):
|
||||||
"""
|
"""
|
||||||
|
|
@ -111,7 +113,7 @@ class ShutdownPlugin(OctoPrintPlugin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AssetPlugin(OctoPrintPlugin):
|
class AssetPlugin(OctoPrintPlugin, ReloadNeedingPlugin):
|
||||||
"""
|
"""
|
||||||
The ``AssetPlugin`` mixin allows plugins to define additional static assets such as Javascript or CSS files to
|
The ``AssetPlugin`` mixin allows plugins to define additional static assets such as Javascript or CSS files to
|
||||||
be automatically embedded into the pages delivered by the server to be used within the client sided part of
|
be automatically embedded into the pages delivered by the server to be used within the client sided part of
|
||||||
|
|
@ -164,7 +166,7 @@ class AssetPlugin(OctoPrintPlugin):
|
||||||
return dict()
|
return dict()
|
||||||
|
|
||||||
|
|
||||||
class TemplatePlugin(OctoPrintPlugin):
|
class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin):
|
||||||
"""
|
"""
|
||||||
Using the ``TemplatePlugin`` mixin plugins may inject their own components into the OctoPrint web interface.
|
Using the ``TemplatePlugin`` mixin plugins may inject their own components into the OctoPrint web interface.
|
||||||
|
|
||||||
|
|
@ -549,7 +551,7 @@ class SimpleApiPlugin(OctoPrintPlugin):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class BlueprintPlugin(OctoPrintPlugin):
|
class BlueprintPlugin(OctoPrintPlugin, RestartNeedingPlugin):
|
||||||
"""
|
"""
|
||||||
The ``BlueprintPlugin`` mixin allows plugins to define their own full fledged endpoints for whatever purpose,
|
The ``BlueprintPlugin`` mixin allows plugins to define their own full fledged endpoints for whatever purpose,
|
||||||
be it a more sophisticated API than what is possible via the :class:`SimpleApiPlugin` or a custom web frontend.
|
be it a more sophisticated API than what is possible via the :class:`SimpleApiPlugin` or a custom web frontend.
|
||||||
|
|
|
||||||
|
|
@ -539,9 +539,15 @@ class Server():
|
||||||
self._logConf = logConf
|
self._logConf = logConf
|
||||||
self._server = None
|
self._server = None
|
||||||
|
|
||||||
|
self._logger = None
|
||||||
|
|
||||||
|
self._lifecycle_callbacks = defaultdict(list)
|
||||||
|
|
||||||
|
self._template_searchpaths = []
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if not self._allowRoot:
|
if not self._allowRoot:
|
||||||
self._checkForRoot()
|
self._check_for_root()
|
||||||
|
|
||||||
global printer
|
global printer
|
||||||
global printerProfileManager
|
global printerProfileManager
|
||||||
|
|
@ -566,12 +572,12 @@ class Server():
|
||||||
settings(init=True, basedir=self._basedir, configfile=self._configfile)
|
settings(init=True, basedir=self._basedir, configfile=self._configfile)
|
||||||
|
|
||||||
# then initialize logging
|
# then initialize logging
|
||||||
self._initLogging(self._debug, self._logConf)
|
self._setup_logging(self._debug, self._logConf)
|
||||||
logger = logging.getLogger(__name__)
|
self._logger = logging.getLogger(__name__)
|
||||||
def exception_logger(exc_type, exc_value, exc_tb):
|
def exception_logger(exc_type, exc_value, exc_tb):
|
||||||
logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_tb))
|
self._logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_tb))
|
||||||
sys.excepthook = exception_logger
|
sys.excepthook = exception_logger
|
||||||
logger.info("Starting OctoPrint %s" % DISPLAY_VERSION)
|
self._logger.info("Starting OctoPrint %s" % DISPLAY_VERSION)
|
||||||
|
|
||||||
# then initialize the plugin manager
|
# then initialize the plugin manager
|
||||||
pluginManager = octoprint.plugin.plugin_manager(init=True)
|
pluginManager = octoprint.plugin.plugin_manager(init=True)
|
||||||
|
|
@ -614,37 +620,25 @@ class Server():
|
||||||
pluginManager.implementation_inject_factories=[octoprint_plugin_inject_factory, settings_plugin_inject_factory]
|
pluginManager.implementation_inject_factories=[octoprint_plugin_inject_factory, settings_plugin_inject_factory]
|
||||||
pluginManager.initialize_implementations()
|
pluginManager.initialize_implementations()
|
||||||
|
|
||||||
def on_plugin_event_factory(text):
|
lifecycleManager = LifecycleManager(self, pluginManager)
|
||||||
def on_plugin_event(name, plugin):
|
|
||||||
logger.info(text.format(**locals()))
|
|
||||||
return on_plugin_event
|
|
||||||
|
|
||||||
pluginManager.on_plugin_loaded = on_plugin_event_factory("Loaded plugin {name}: {plugin}")
|
|
||||||
pluginManager.on_plugin_unloaded = on_plugin_event_factory("Unloaded plugin {name}: {plugin}")
|
|
||||||
pluginManager.on_plugin_activated = on_plugin_event_factory("Activated plugin {name}: {plugin}")
|
|
||||||
pluginManager.on_plugin_deactivated = on_plugin_event_factory("Deactivated plugin {name}: {plugin}")
|
|
||||||
pluginManager.on_plugin_enabled = on_plugin_event_factory("Enabled plugin {name}: {plugin}")
|
|
||||||
pluginManager.on_plugin_disabled = on_plugin_event_factory("Disabled plugin {name}: {plugin}")
|
|
||||||
|
|
||||||
pluginManager.log_all_plugins()
|
pluginManager.log_all_plugins()
|
||||||
|
|
||||||
|
# initialize slicing manager and register it for changes in the registered plugins
|
||||||
slicingManager.initialize()
|
slicingManager.initialize()
|
||||||
|
lifecycleManager.add_callback(["enabled", "disabled"], lambda name, plugin: slicingManager.reload_slicers())
|
||||||
|
|
||||||
# configure additional template folders for jinja2
|
# setup jinja2
|
||||||
template_plugins = pluginManager.get_implementations(octoprint.plugin.TemplatePlugin)
|
self._setup_jinja2()
|
||||||
additional_template_folders = []
|
def template_enabled(name, plugin):
|
||||||
for plugin in template_plugins:
|
if plugin.implementation is None or not isinstance(plugin.implementation, octoprint.plugin.TemplatePlugin):
|
||||||
folder = plugin.get_template_folder()
|
return
|
||||||
if folder is not None:
|
self._register_additional_template_plugin(plugin.implementation)
|
||||||
additional_template_folders.append(plugin.get_template_folder())
|
def template_disabled(name, plugin):
|
||||||
|
if plugin.implementation is None or not isinstance(plugin.implementation, octoprint.plugin.TemplatePlugin):
|
||||||
import jinja2
|
return
|
||||||
jinja_loader = jinja2.ChoiceLoader([
|
self._unregister_additional_template_plugin(plugin.implementation)
|
||||||
app.jinja_loader,
|
lifecycleManager.add_callback("enabled", template_enabled)
|
||||||
jinja2.FileSystemLoader(additional_template_folders)
|
lifecycleManager.add_callback("disabled", template_disabled)
|
||||||
])
|
|
||||||
app.jinja_loader = jinja_loader
|
|
||||||
del jinja2
|
|
||||||
app.jinja_env.add_extension("jinja2.ext.do")
|
|
||||||
|
|
||||||
# configure timelapse
|
# configure timelapse
|
||||||
octoprint.timelapse.configureTimelapse()
|
octoprint.timelapse.configureTimelapse()
|
||||||
|
|
@ -660,7 +654,7 @@ class Server():
|
||||||
clazz = octoprint.util.get_class(userManagerName)
|
clazz = octoprint.util.get_class(userManagerName)
|
||||||
userManager = clazz()
|
userManager = clazz()
|
||||||
except AttributeError, e:
|
except AttributeError, e:
|
||||||
logger.exception("Could not instantiate user manager %s, will run with accessControl disabled!" % userManagerName)
|
self._logger.exception("Could not instantiate user manager %s, will run with accessControl disabled!" % userManagerName)
|
||||||
|
|
||||||
app.wsgi_app = util.ReverseProxied(
|
app.wsgi_app = util.ReverseProxied(
|
||||||
app.wsgi_app,
|
app.wsgi_app,
|
||||||
|
|
@ -696,36 +690,20 @@ class Server():
|
||||||
|
|
||||||
app.debug = self._debug
|
app.debug = self._debug
|
||||||
|
|
||||||
from octoprint.server.api import api
|
|
||||||
from octoprint.server.apps import apps
|
|
||||||
|
|
||||||
# register API blueprint
|
# register API blueprint
|
||||||
app.register_blueprint(api, url_prefix="/api")
|
self._setup_blueprints()
|
||||||
app.register_blueprint(apps, url_prefix="/apps")
|
def blueprint_enabled(name, plugin):
|
||||||
|
if plugin.implementation is None or not isinstance(plugin.implementation, octoprint.plugin.BlueprintPlugin):
|
||||||
# also register any blueprints defined in BlueprintPlugins
|
return
|
||||||
blueprint_plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.BlueprintPlugin)
|
self._register_blueprint_plugin(plugin.implementation)
|
||||||
for plugin in blueprint_plugins:
|
lifecycleManager.add_callback(["enabled"], blueprint_enabled)
|
||||||
name = plugin._identifier
|
|
||||||
blueprint = plugin.get_blueprint()
|
|
||||||
if blueprint is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if plugin.is_blueprint_protected():
|
|
||||||
from octoprint.server.util import apiKeyRequestHandler, corsResponseHandler
|
|
||||||
blueprint.before_request(apiKeyRequestHandler)
|
|
||||||
blueprint.after_request(corsResponseHandler)
|
|
||||||
|
|
||||||
url_prefix = "/plugin/{name}".format(name=name)
|
|
||||||
app.register_blueprint(blueprint, url_prefix=url_prefix)
|
|
||||||
logger.debug("Registered API of plugin {name} under URL prefix {url_prefix}".format(name=name, url_prefix=url_prefix))
|
|
||||||
|
|
||||||
## Tornado initialization starts here
|
## Tornado initialization starts here
|
||||||
|
|
||||||
ioloop = IOLoop()
|
ioloop = IOLoop()
|
||||||
ioloop.install()
|
ioloop.install()
|
||||||
|
|
||||||
self._router = SockJSRouter(self._createSocketConnection, "/sockjs")
|
self._router = SockJSRouter(self._create_socket_connection, "/sockjs")
|
||||||
|
|
||||||
upload_suffixes = dict(name=settings().get(["server", "uploads", "nameSuffix"]), path=settings().get(["server", "uploads", "pathSuffix"]))
|
upload_suffixes = dict(name=settings().get(["server", "uploads", "nameSuffix"]), path=settings().get(["server", "uploads", "pathSuffix"]))
|
||||||
self._tornado_app = Application(self._router.urls + [
|
self._tornado_app = Application(self._router.urls + [
|
||||||
|
|
@ -759,9 +737,16 @@ class Server():
|
||||||
"on_startup",
|
"on_startup",
|
||||||
args=(self._host, self._port))
|
args=(self._host, self._port))
|
||||||
|
|
||||||
|
def call_on_startup(name, plugin):
|
||||||
|
implementation = plugin.get_implementation(octoprint.plugin.StartupPlugin)
|
||||||
|
if implementation is None:
|
||||||
|
return
|
||||||
|
implementation.on_startup(self._host, self._port)
|
||||||
|
lifecycleManager.add_callback("enabled", call_on_startup)
|
||||||
|
|
||||||
# prepare our after startup function
|
# prepare our after startup function
|
||||||
def on_after_startup():
|
def on_after_startup():
|
||||||
logger.info("Listening on http://%s:%d" % (self._host, self._port))
|
self._logger.info("Listening on http://%s:%d" % (self._host, self._port))
|
||||||
|
|
||||||
# now this is somewhat ugly, but the issue is the following: startup plugins might want to do things for
|
# now this is somewhat ugly, but the issue is the following: startup plugins might want to do things for
|
||||||
# which they need the server to be already alive (e.g. for being able to resolve urls, such as favicons
|
# which they need the server to be already alive (e.g. for being able to resolve urls, such as favicons
|
||||||
|
|
@ -771,13 +756,21 @@ class Server():
|
||||||
def work():
|
def work():
|
||||||
octoprint.plugin.call_plugin(octoprint.plugin.StartupPlugin,
|
octoprint.plugin.call_plugin(octoprint.plugin.StartupPlugin,
|
||||||
"on_after_startup")
|
"on_after_startup")
|
||||||
|
|
||||||
|
def call_on_after_startup(name, plugin):
|
||||||
|
implementation = plugin.get_implementation(octoprint.plugin.StartupPlugin)
|
||||||
|
if implementation is None:
|
||||||
|
return
|
||||||
|
implementation.on_after_startup()
|
||||||
|
lifecycleManager.add_callback("enabled", call_on_after_startup)
|
||||||
|
|
||||||
import threading
|
import threading
|
||||||
threading.Thread(target=work).start()
|
threading.Thread(target=work).start()
|
||||||
ioloop.add_callback(on_after_startup)
|
ioloop.add_callback(on_after_startup)
|
||||||
|
|
||||||
# prepare our shutdown function
|
# prepare our shutdown function
|
||||||
def on_shutdown():
|
def on_shutdown():
|
||||||
logger.info("Goodbye!")
|
self._logger.info("Goodbye!")
|
||||||
observer.stop()
|
observer.stop()
|
||||||
observer.join()
|
observer.join()
|
||||||
octoprint.plugin.call_plugin(octoprint.plugin.ShutdownPlugin,
|
octoprint.plugin.call_plugin(octoprint.plugin.ShutdownPlugin,
|
||||||
|
|
@ -789,18 +782,18 @@ class Server():
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
except:
|
except:
|
||||||
logger.fatal("Now that is embarrassing... Something really really went wrong here. Please report this including the stacktrace below in OctoPrint's bugtracker. Thanks!")
|
self._logger.fatal("Now that is embarrassing... Something really really went wrong here. Please report this including the stacktrace below in OctoPrint's bugtracker. Thanks!")
|
||||||
logger.exception("Stacktrace follows:")
|
self._logger.exception("Stacktrace follows:")
|
||||||
|
|
||||||
def _createSocketConnection(self, session):
|
def _create_socket_connection(self, session):
|
||||||
global printer, fileManager, analysisQueue, userManager, eventManager
|
global printer, fileManager, analysisQueue, userManager, eventManager
|
||||||
return util.sockjs.PrinterStateConnection(printer, fileManager, analysisQueue, userManager, eventManager, pluginManager, session)
|
return util.sockjs.PrinterStateConnection(printer, fileManager, analysisQueue, userManager, eventManager, pluginManager, session)
|
||||||
|
|
||||||
def _checkForRoot(self):
|
def _check_for_root(self):
|
||||||
if "geteuid" in dir(os) and os.geteuid() == 0:
|
if "geteuid" in dir(os) and os.geteuid() == 0:
|
||||||
exit("You should not run OctoPrint as root!")
|
exit("You should not run OctoPrint as root!")
|
||||||
|
|
||||||
def _initLogging(self, debug, logConf=None):
|
def _setup_logging(self, debug, logConf=None):
|
||||||
defaultConfig = {
|
defaultConfig = {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"formatters": {
|
"formatters": {
|
||||||
|
|
@ -871,6 +864,115 @@ class Server():
|
||||||
logging.getLogger("SERIAL").setLevel(logging.DEBUG)
|
logging.getLogger("SERIAL").setLevel(logging.DEBUG)
|
||||||
logging.getLogger("SERIAL").debug("Enabling serial logging")
|
logging.getLogger("SERIAL").debug("Enabling serial logging")
|
||||||
|
|
||||||
|
def _setup_jinja2(self):
|
||||||
|
app.jinja_env.add_extension("jinja2.ext.do")
|
||||||
|
|
||||||
|
# configure additional template folders for jinja2
|
||||||
|
import jinja2
|
||||||
|
filesystem_loader = jinja2.FileSystemLoader([])
|
||||||
|
filesystem_loader.searchpath = self._template_searchpaths
|
||||||
|
|
||||||
|
jinja_loader = jinja2.ChoiceLoader([
|
||||||
|
app.jinja_loader,
|
||||||
|
filesystem_loader
|
||||||
|
])
|
||||||
|
app.jinja_loader = jinja_loader
|
||||||
|
del jinja2
|
||||||
|
|
||||||
|
self._register_template_plugins()
|
||||||
|
|
||||||
|
def _register_template_plugins(self):
|
||||||
|
template_plugins = pluginManager.get_implementations(octoprint.plugin.TemplatePlugin)
|
||||||
|
for plugin in template_plugins:
|
||||||
|
self._register_additional_template_plugin(plugin)
|
||||||
|
|
||||||
|
def _register_additional_template_plugin(self, plugin):
|
||||||
|
folder = plugin.get_template_folder()
|
||||||
|
if folder is not None and not folder in self._template_searchpaths:
|
||||||
|
self._template_searchpaths.append(folder)
|
||||||
|
|
||||||
|
def _unregister_additional_template_plugin(self, plugin):
|
||||||
|
folder = plugin.get_template_folder()
|
||||||
|
if folder is not None and folder in self._template_searchpaths:
|
||||||
|
self._template_searchpaths.remove(folder)
|
||||||
|
|
||||||
|
def _setup_blueprints(self):
|
||||||
|
from octoprint.server.api import api
|
||||||
|
from octoprint.server.apps import apps
|
||||||
|
|
||||||
|
app.register_blueprint(api, url_prefix="/api")
|
||||||
|
app.register_blueprint(apps, url_prefix="/apps")
|
||||||
|
|
||||||
|
# also register any blueprints defined in BlueprintPlugins
|
||||||
|
self._register_blueprint_plugins()
|
||||||
|
|
||||||
|
def _register_blueprint_plugins(self):
|
||||||
|
blueprint_plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.BlueprintPlugin)
|
||||||
|
for plugin in blueprint_plugins:
|
||||||
|
self._register_blueprint_plugin(plugin)
|
||||||
|
|
||||||
|
def _register_blueprint_plugin(self, plugin):
|
||||||
|
name = plugin._identifier
|
||||||
|
blueprint = plugin.get_blueprint()
|
||||||
|
if blueprint is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if plugin.is_blueprint_protected():
|
||||||
|
from octoprint.server.util import apiKeyRequestHandler, corsResponseHandler
|
||||||
|
blueprint.before_request(apiKeyRequestHandler)
|
||||||
|
blueprint.after_request(corsResponseHandler)
|
||||||
|
|
||||||
|
url_prefix = "/plugin/{name}".format(name=name)
|
||||||
|
app.register_blueprint(blueprint, url_prefix=url_prefix)
|
||||||
|
|
||||||
|
if self._logger:
|
||||||
|
self._logger.debug("Registered API of plugin {name} under URL prefix {url_prefix}".format(name=name, url_prefix=url_prefix))
|
||||||
|
|
||||||
|
class LifecycleManager(object):
|
||||||
|
def __init__(self, server, plugin_manager):
|
||||||
|
self._server = server
|
||||||
|
self._plugin_manager = plugin_manager
|
||||||
|
|
||||||
|
self._plugin_lifecycle_callbacks = defaultdict(list)
|
||||||
|
self._logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def on_plugin_event_factory(lifecycle_event, text):
|
||||||
|
def on_plugin_event(name, plugin):
|
||||||
|
self.on_plugin_event(lifecycle_event, name, plugin)
|
||||||
|
self._logger.debug(text.format(**locals()))
|
||||||
|
return on_plugin_event
|
||||||
|
|
||||||
|
self._plugin_manager.on_plugin_loaded = on_plugin_event_factory("loaded", "Loaded plugin {name}: {plugin}")
|
||||||
|
self._plugin_manager.on_plugin_unloaded = on_plugin_event_factory("unloaded", "Unloaded plugin {name}: {plugin}")
|
||||||
|
self._plugin_manager.on_plugin_activated = on_plugin_event_factory("activated", "Activated plugin {name}: {plugin}")
|
||||||
|
self._plugin_manager.on_plugin_deactivated = on_plugin_event_factory("deactivated", "Deactivated plugin {name}: {plugin}")
|
||||||
|
self._plugin_manager.on_plugin_enabled = on_plugin_event_factory("enabled", "Enabled plugin {name}: {plugin}")
|
||||||
|
self._plugin_manager.on_plugin_disabled = on_plugin_event_factory("disabled", "Disabled plugin {name}: {plugin}")
|
||||||
|
|
||||||
|
def on_plugin_event(self, event, name, plugin):
|
||||||
|
for lifecycle_callback in self._plugin_lifecycle_callbacks[event]:
|
||||||
|
lifecycle_callback(name, plugin)
|
||||||
|
|
||||||
|
def add_callback(self, events, callback):
|
||||||
|
if isinstance(events, (str, unicode)):
|
||||||
|
events = [events]
|
||||||
|
|
||||||
|
for event in events:
|
||||||
|
self._plugin_lifecycle_callbacks[event].append(callback)
|
||||||
|
|
||||||
|
def remove_callback(self, callback, events=None):
|
||||||
|
if events is None:
|
||||||
|
for event in self._plugin_lifecycle_callbacks:
|
||||||
|
if callback in self._plugin_lifecycle_callbacks[event]:
|
||||||
|
self._plugin_lifecycle_callbacks[event].remove(callback)
|
||||||
|
else:
|
||||||
|
if isinstance(events, (str, unicode)):
|
||||||
|
events = [events]
|
||||||
|
|
||||||
|
for event in events:
|
||||||
|
if callback in self._plugin_lifecycle_callbacks[event]:
|
||||||
|
self._plugin_lifecycle_callbacks[event].remove(callback)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
server = Server()
|
server = Server()
|
||||||
server.run()
|
server.run()
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,9 @@ default_settings = {
|
||||||
{ "name": "Suppress M105 requests/responses", "regex": "(Send: M105)|(Recv: ok (B|T\d*):)" },
|
{ "name": "Suppress M105 requests/responses", "regex": "(Send: M105)|(Recv: ok (B|T\d*):)" },
|
||||||
{ "name": "Suppress M27 requests/responses", "regex": "(Send: M27)|(Recv: SD printing byte)" }
|
{ "name": "Suppress M27 requests/responses", "regex": "(Send: M27)|(Recv: SD printing byte)" }
|
||||||
],
|
],
|
||||||
"plugins": {},
|
"plugins": {
|
||||||
|
"_disabled": []
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"gcode": {
|
"gcode": {
|
||||||
"afterPrintCancelled": "; disable motors\nM84\n\n;disable all heaters\n{% snippet 'disable_hotends' %}\nM140 S0\n\n;disable fan\nM106 S0",
|
"afterPrintCancelled": "; disable motors\nM84\n\n;disable all heaters\n{% snippet 'disable_hotends' %}\nM140 S0\n\n;disable fan\nM106 S0",
|
||||||
|
|
|
||||||
|
|
@ -113,16 +113,18 @@ class SlicingManager(object):
|
||||||
Initializes the slicing manager by loading and initializing all available
|
Initializes the slicing manager by loading and initializing all available
|
||||||
:class:`~octoprint.plugin.SlicerPlugin` implementations.
|
:class:`~octoprint.plugin.SlicerPlugin` implementations.
|
||||||
"""
|
"""
|
||||||
self._load_slicers()
|
self.reload_slicers()
|
||||||
|
|
||||||
def _load_slicers(self):
|
def reload_slicers(self):
|
||||||
"""
|
"""
|
||||||
Retrieves all registered :class:`~octoprint.plugin.SlicerPlugin` implementations and registers them as
|
Retrieves all registered :class:`~octoprint.plugin.SlicerPlugin` implementations and registers them as
|
||||||
available slicers.
|
available slicers.
|
||||||
"""
|
"""
|
||||||
plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SlicerPlugin)
|
plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SlicerPlugin)
|
||||||
|
slicers = dict()
|
||||||
for plugin in plugins:
|
for plugin in plugins:
|
||||||
self._slicers[plugin.get_slicer_properties()["type"]] = plugin
|
slicers[plugin.get_slicer_properties()["type"]] = plugin
|
||||||
|
self._slicers = slicers
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def slicing_enabled(self):
|
def slicing_enabled(self):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue