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:
Gina Häußge 2015-04-02 23:02:42 +02:00
parent f0b48a6b43
commit 97aecdf4cf
6 changed files with 371 additions and 168 deletions

View file

@ -88,11 +88,7 @@ def plugin_manager(init=False, plugin_folders=None, plugin_types=None, plugin_en
if plugin_entry_points is None:
plugin_entry_points = "octoprint.plugin"
if plugin_disabled_list is None:
all_plugin_settings = settings().get(["plugins"])
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)
plugin_disabled_list = settings().get(["plugins", "_disabled"])
_instance = PluginManager(plugin_folders, plugin_types, plugin_entry_points, logging_prefix="octoprint.plugins.", plugin_disabled_list=plugin_disabled_list)
else:

View file

@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms
import os
import imp
from collections import defaultdict
from collections import defaultdict, namedtuple
import logging
@ -113,6 +113,7 @@ class PluginInfo(object):
self.origin = None
self.enabled = True
self.bundled = False
self.loaded = False
self._name = name
self._version = version
@ -385,26 +386,34 @@ class PluginManager(object):
self.on_plugin_loaded = 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_disabled = lambda *args, **kwargs: None
self.on_plugin_implementations_initialized = lambda *args, **kwargs: None
self.registered_clients = []
self.reload_plugins()
self.reload_plugins(startup=True, initialize_implementations=False)
def _find_plugins(self):
plugins = dict()
disabled_plugins = dict()
@property
def all_plugins(self):
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:
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:
self._add_plugins_from_entry_points(self.plugin_entry_points, plugins, disabled_plugins)
return plugins, disabled_plugins
result.update(self._find_plugins_from_entry_points(self.plugin_entry_points, existing))
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:
readonly = False
if isinstance(folder, (list, tuple)):
@ -427,7 +436,7 @@ class PluginManager(object):
else:
continue
if key in plugins:
if key in existing or key in result:
# plugin is already defined, ignore it
continue
@ -437,28 +446,31 @@ class PluginManager(object):
if readonly:
plugin.bundled = True
if self._is_plugin_disabled(key):
plugin.enabled = False
disabled_plugins[key] = plugin
else:
plugins[key] = plugin
plugin.enabled = False
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 pkginfo
# let's make sure we have a current working set
working_set = pkg_resources.WorkingSet()
if not isinstance(groups, (list, tuple)):
groups = [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
module_name = entry_point.module_name
version = entry_point.dist.version
if key in plugins:
if key in existing or key in result:
# plugin is already defined, ignore it
continue
@ -479,14 +491,10 @@ class PluginManager(object):
plugin = self._import_plugin_from_module(key, **kwargs)
if plugin:
plugin.origin = ("entry_point", group, module_name)
plugin.enabled = False
result[key] = plugin
if self._is_plugin_disabled(key):
plugin.enabled = False
disabled_plugins[key] = plugin
else:
plugins[key] = plugin
return plugins, disabled_plugins
return result
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
@ -502,12 +510,14 @@ class PluginManager(object):
return None
plugin = self._import_plugin(key, *module, name=name, version=version, summary=summary, author=author, url=url, license=license)
if plugin:
if plugin.check():
return plugin
else:
self.logger.warn("Plugin \"{plugin}\" did not pass check".format(plugin=str(plugin)))
return None
if plugin is None:
return None
if plugin.check():
return plugin
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):
@ -515,22 +525,27 @@ class PluginManager(object):
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)
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
def _is_plugin_disabled(self, key):
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(
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():
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:
self.logger.info("No plugins found")
@ -541,111 +556,158 @@ class PluginManager(object):
hooks=sum(map(lambda x: len(x), self.plugin_hooks.values()))
))
@property
def all_plugins(self):
plugins = dict(self.plugins)
plugins.update(self.disabled_plugins)
return plugins
def load_plugin(self, name, plugin=None, startup=False, initialize_implementation=True):
if not name in self.all_plugins:
self.logger.warn("Trying to load an unknown plugin {name}".format(**locals()))
return
if plugin is None:
plugin = self.all_plugins[name]
def load_plugin(self, name, plugin):
try:
plugin.load()
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:
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):
if not name in self.plugins:
if name in self.disabled_plugins:
plugin = self.disabled_plugins[name]
else:
self.logger.warn("Trying to unload unknown plugin {name}".format(**locals()))
return
else:
plugin = self.plugins[name]
if not name in self.all_plugins:
self.logger.warn("Trying to unload unknown plugin {name}".format(**locals()))
return
plugin = self.all_plugins[name]
try:
self._deactivate_plugin(name, plugin)
if plugin.enabled:
self.disable_plugin(name, plugin=plugin)
plugin.unload()
self.on_plugin_unloaded(name, plugin)
if name in self.plugins:
del self.plugins[name]
if name in self.disabled_plugins:
del self.disabled_plugins[name]
plugin.loaded = False
self.logger.debug("Unloaded plugin {name}: {plugin}".format(**locals()))
except PluginLifecycleException as e:
raise e
except:
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:
self.logger.warn("Tried to enable plugin {name}, however it is not disabled".format(**locals()))
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:
del self.disabled_plugins[name]
plugin.enable()
self._activate_plugin(name, plugin)
self.initialize_implementation_of_plugin(name, plugin)
self.plugins[name] = plugin
except PluginLifecycleException as e:
raise e
except:
self.logger.exception("There was an error while enabling plugin {name}".format(**locals()))
self.disabled_plugins[name] = plugin
return False
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.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:
self.logger.warn("Tried to disable plugin {name}, however it is not enabled".format(**locals()))
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:
del self.plugins[name]
plugin.disable()
self._deactivate_plugin(name, plugin)
self.disabled_plugins[name] = plugin
except PluginLifecycleException as e:
raise e
except:
self.logger.exception("There was an error while disabling plugin {name}".format(**locals()))
self.plugins[name] = plugin
return False
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.logger.debug("Disabled plugin {name}: {plugin}".format(**locals()))
def _activate_plugin(self, name, plugin):
plugin.activate()
return True
def _activate_plugin(self, name, plugin):
# evaluate registered hooks
for hook, callback in plugin.hooks.items():
self.plugin_hooks[hook].append((name, callback))
# evaluate registered implementation
if plugin.implementation:
if isinstance(plugin.implementation, RestartNeedingPlugin):
plugin.hotchangeable = False
for plugin_type in self.plugin_types:
if isinstance(plugin.implementation, plugin_type):
self.plugin_implementations_by_type[plugin_type].append((name, plugin.implementation))
self.plugin_implementations[name] = plugin.implementation
self.on_plugin_activated(name, plugin)
def _deactivate_plugin(self, name, plugin):
plugin.deactivate()
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:
del self.plugin_implementations[name]
if name in self.plugin_implementations:
del self.plugin_implementations[name]
for plugin_type in self.plugin_types:
try:
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
pass
self.on_plugin_deactivated(name, plugin)
def initialize_implementations(self, additional_injects=None, additional_inject_factories=None):
for name, plugin in self.plugins.items():
self.initialize_implementation_of_plugin(name, plugin,
@ -667,7 +727,7 @@ class PluginManager(object):
if plugin.implementation is None:
return
self.initialize_implementation(name, plugin, plugin.implementation,
return self.initialize_implementation(name, plugin, plugin.implementation,
additional_injects=additional_injects,
additional_inject_factories=additional_inject_factories)
@ -710,18 +770,22 @@ class PluginManager(object):
for arg, value in return_value.items():
setattr(implementation, "_" + arg, value)
result = implementation.initialize()
if result is not None and not result:
self.logger.warn("Initialization of {name} returned False, disabling it".format(**locals()))
self._deactivate_plugin(name, plugin)
return
except:
self.logger.exception("Exception while initializing plugin {name}, disabling it".format(**locals()))
implementation.initialize()
except Exception as e:
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:
self.on_plugin_implementations_initialized(name, plugin)
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=(" ", "!")):
@ -927,3 +991,38 @@ class Plugin(object):
Called by the plugin core after performing all injections. Override this to initialize your implementation.
"""
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}")

View file

@ -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"
from .core import Plugin
from .core import (Plugin, RestartNeedingPlugin)
class OctoPrintPlugin(Plugin):
@ -68,6 +68,8 @@ class OctoPrintPlugin(Plugin):
pass
class ReloadNeedingPlugin(Plugin):
pass
class StartupPlugin(OctoPrintPlugin):
"""
@ -111,7 +113,7 @@ class ShutdownPlugin(OctoPrintPlugin):
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
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()
class TemplatePlugin(OctoPrintPlugin):
class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin):
"""
Using the ``TemplatePlugin`` mixin plugins may inject their own components into the OctoPrint web interface.
@ -549,7 +551,7 @@ class SimpleApiPlugin(OctoPrintPlugin):
return None
class BlueprintPlugin(OctoPrintPlugin):
class BlueprintPlugin(OctoPrintPlugin, RestartNeedingPlugin):
"""
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.

View file

@ -539,9 +539,15 @@ class Server():
self._logConf = logConf
self._server = None
self._logger = None
self._lifecycle_callbacks = defaultdict(list)
self._template_searchpaths = []
def run(self):
if not self._allowRoot:
self._checkForRoot()
self._check_for_root()
global printer
global printerProfileManager
@ -566,12 +572,12 @@ class Server():
settings(init=True, basedir=self._basedir, configfile=self._configfile)
# then initialize logging
self._initLogging(self._debug, self._logConf)
logger = logging.getLogger(__name__)
self._setup_logging(self._debug, self._logConf)
self._logger = logging.getLogger(__name__)
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
logger.info("Starting OctoPrint %s" % DISPLAY_VERSION)
self._logger.info("Starting OctoPrint %s" % DISPLAY_VERSION)
# then initialize the plugin manager
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.initialize_implementations()
def on_plugin_event_factory(text):
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}")
lifecycleManager = LifecycleManager(self, pluginManager)
pluginManager.log_all_plugins()
# initialize slicing manager and register it for changes in the registered plugins
slicingManager.initialize()
lifecycleManager.add_callback(["enabled", "disabled"], lambda name, plugin: slicingManager.reload_slicers())
# configure additional template folders for jinja2
template_plugins = pluginManager.get_implementations(octoprint.plugin.TemplatePlugin)
additional_template_folders = []
for plugin in template_plugins:
folder = plugin.get_template_folder()
if folder is not None:
additional_template_folders.append(plugin.get_template_folder())
import jinja2
jinja_loader = jinja2.ChoiceLoader([
app.jinja_loader,
jinja2.FileSystemLoader(additional_template_folders)
])
app.jinja_loader = jinja_loader
del jinja2
app.jinja_env.add_extension("jinja2.ext.do")
# setup jinja2
self._setup_jinja2()
def template_enabled(name, plugin):
if plugin.implementation is None or not isinstance(plugin.implementation, octoprint.plugin.TemplatePlugin):
return
self._register_additional_template_plugin(plugin.implementation)
def template_disabled(name, plugin):
if plugin.implementation is None or not isinstance(plugin.implementation, octoprint.plugin.TemplatePlugin):
return
self._unregister_additional_template_plugin(plugin.implementation)
lifecycleManager.add_callback("enabled", template_enabled)
lifecycleManager.add_callback("disabled", template_disabled)
# configure timelapse
octoprint.timelapse.configureTimelapse()
@ -660,7 +654,7 @@ class Server():
clazz = octoprint.util.get_class(userManagerName)
userManager = clazz()
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,
@ -696,36 +690,20 @@ class Server():
app.debug = self._debug
from octoprint.server.api import api
from octoprint.server.apps import apps
# register API blueprint
app.register_blueprint(api, url_prefix="/api")
app.register_blueprint(apps, url_prefix="/apps")
# also register any blueprints defined in BlueprintPlugins
blueprint_plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.BlueprintPlugin)
for plugin in blueprint_plugins:
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))
self._setup_blueprints()
def blueprint_enabled(name, plugin):
if plugin.implementation is None or not isinstance(plugin.implementation, octoprint.plugin.BlueprintPlugin):
return
self._register_blueprint_plugin(plugin.implementation)
lifecycleManager.add_callback(["enabled"], blueprint_enabled)
## Tornado initialization starts here
ioloop = IOLoop()
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"]))
self._tornado_app = Application(self._router.urls + [
@ -759,9 +737,16 @@ class Server():
"on_startup",
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
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
# 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():
octoprint.plugin.call_plugin(octoprint.plugin.StartupPlugin,
"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
threading.Thread(target=work).start()
ioloop.add_callback(on_after_startup)
# prepare our shutdown function
def on_shutdown():
logger.info("Goodbye!")
self._logger.info("Goodbye!")
observer.stop()
observer.join()
octoprint.plugin.call_plugin(octoprint.plugin.ShutdownPlugin,
@ -789,18 +782,18 @@ class Server():
except KeyboardInterrupt:
pass
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!")
logger.exception("Stacktrace follows:")
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!")
self._logger.exception("Stacktrace follows:")
def _createSocketConnection(self, session):
def _create_socket_connection(self, session):
global printer, fileManager, analysisQueue, userManager, eventManager
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:
exit("You should not run OctoPrint as root!")
def _initLogging(self, debug, logConf=None):
def _setup_logging(self, debug, logConf=None):
defaultConfig = {
"version": 1,
"formatters": {
@ -871,6 +864,115 @@ class Server():
logging.getLogger("SERIAL").setLevel(logging.DEBUG)
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__":
server = Server()
server.run()

View file

@ -224,7 +224,9 @@ default_settings = {
{ "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)" }
],
"plugins": {},
"plugins": {
"_disabled": []
},
"scripts": {
"gcode": {
"afterPrintCancelled": "; disable motors\nM84\n\n;disable all heaters\n{% snippet 'disable_hotends' %}\nM140 S0\n\n;disable fan\nM106 S0",

View file

@ -113,16 +113,18 @@ class SlicingManager(object):
Initializes the slicing manager by loading and initializing all available
: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
available slicers.
"""
plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SlicerPlugin)
slicers = dict()
for plugin in plugins:
self._slicers[plugin.get_slicer_properties()["type"]] = plugin
slicers[plugin.get_slicer_properties()["type"]] = plugin
self._slicers = slicers
@property
def slicing_enabled(self):