From a65ad50898ec0fc5d36738c88a873aa04f88c614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 13 Jul 2015 13:51:36 +0200 Subject: [PATCH] Better command line interface --- scripts/octoprint.default | 10 +- scripts/octoprint.init | 29 ++- setup.py | 3 +- src/octoprint/__init__.py | 334 +++++++++++++++++++++++++------ src/octoprint/plugin/__init__.py | 20 +- src/octoprint/plugin/core.py | 20 +- src/octoprint/server/__init__.py | 192 ++++++------------ 7 files changed, 388 insertions(+), 220 deletions(-) diff --git a/scripts/octoprint.default b/scripts/octoprint.default index 2d9a46d7fc..1f58092a63 100644 --- a/scripts/octoprint.default +++ b/scripts/octoprint.default @@ -3,11 +3,17 @@ # The init.d script will only run if this variable non-empty. OCTOPRINT_USER=pi +# base directory to use +#BASEDIR=/home/pi/.octoprint + +# configuration file to use +#CONFIGFILE=/home/pi/.octoprint/config.yaml + # On what port to run daemon, default is 5000 PORT=5000 -# Path to the OctoPrint executable, use this to override the default setting "/usr/bin/octoprint" -#DAEMON=/path/to/octoprint/executable +# Path to the OctoPrint executable, you need to set this to match your installation! +#DAEMON=/home/pi/OctoPrint/venv/bin/octoprint # What arguments to pass to octoprint, usually no need to touch this DAEMON_ARGS="--port=$PORT" diff --git a/scripts/octoprint.init b/scripts/octoprint.init index b0916fae93..e1079b4e45 100644 --- a/scripts/octoprint.init +++ b/scripts/octoprint.init @@ -16,17 +16,23 @@ # Author: Sami Olmari PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin -DESC="Octoprint Daemon" -NAME="Octoprint" -DAEMON=/usr/local/bin/octoprint -PIDFILE=/var/run/$NAME.pid +DESC="OctoPrint Daemon" +NAME="OctoPrint" PKGNAME=octoprint +PIDFILE=/var/run/$PKGNAME.pid SCRIPTNAME=/etc/init.d/$PKGNAME # Read configuration variable file if it is present [ -r /etc/default/$PKGNAME ] && . /etc/default/$PKGNAME -# Exit if the octoprint is not installed +# Exit if the DAEMON is not set +if [ -z "$DAEMON" ] +then + log_warning_msg "Not starting $PKGNAME, DAEMON not set in /etc/default/$PKGNAME." + exit 0 +fi + +# Exit if the DAEMON is not installed [ -x "$DAEMON" ] || exit 0 # Load the VERBOSE setting and other rcS variables @@ -48,6 +54,17 @@ then exit 0 fi +COMMAND_ARGS= +if [ -z "$BASEDIR" ] +then + COMMAND_ARGS="--basedir $BASEDIR $COMMAND_ARGS" +fi + +if [ -z "$CONFIGFILE" ] +then + COMMAND_ARGS="--config $CONFIGFILE $COMMAND_ARGS" +fi + # # Function to verify if a pid is alive # @@ -74,7 +91,7 @@ do_start() if [ $RETVAL != 0 ]; then start-stop-daemon --start --background --quiet --pidfile $PIDFILE --make-pidfile \ --exec $DAEMON --chuid $OCTOPRINT_USER --user $OCTOPRINT_USER --umask $UMASK --nicelevel=$NICELEVEL \ - -- $DAEMON_ARGS + -- $COMMAND_ARGS serve $DAEMON_ARGS RETVAL="$?" fi } diff --git a/setup.py b/setup.py index 7d4956f6d9..ed83e2675e 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,8 @@ "rsa", "pkginfo", "requests", - "semantic_version" + "semantic_version", + "Click" ] # Additional requirements for optional install options diff --git a/src/octoprint/__init__.py b/src/octoprint/__init__.py index 67baa4f741..b00e7646b4 100644 --- a/src/octoprint/__init__.py +++ b/src/octoprint/__init__.py @@ -1,5 +1,14 @@ #!/usr/bin/env python +# coding=utf-8 +from __future__ import absolute_import, print_function + +__author__ = "Gina Häußge " +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' +__copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms of the AGPLv3 License" + import sys +import click + from octoprint.daemon import Daemon from octoprint.server import Server @@ -9,76 +18,283 @@ __version__ = get_versions()['version'] del get_versions -#~~ main class -class Main(Daemon): - def __init__(self, pidfile, configfile, basedir, host, port, debug, allowRoot, logConf): +def init_platform(basedir, configfile, use_logging_file=True, logging_file=None, + logging_config=None, debug=False, uncaught_logger=None, + uncaught_handler=None): + settings = init_settings(basedir, configfile) + logger = init_logging(settings, + use_logging_file=use_logging_file, + logging_file=logging_file, + default_config=logging_config, + debug=debug, + uncaught_logger=uncaught_logger, + uncaught_handler=uncaught_handler) + plugin_manager = init_pluginsystem(settings) + return settings, logger, plugin_manager + + +def init_settings(basedir, configfile): + from octoprint.settings import settings + return settings(init=True, basedir=basedir, configfile=configfile) + + +def init_logging(settings, use_logging_file=True, logging_file=None, default_config=None, debug=False, uncaught_logger=None, uncaught_handler=None): + import logging + import os + + from octoprint.util import dict_merge + + # default logging configuration + if default_config is None: + default_config = { + "version": 1, + "formatters": { + "simple": { + "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + } + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "level": "DEBUG", + "formatter": "simple", + "stream": "ext://sys.stdout" + }, + "file": { + "class": "logging.handlers.TimedRotatingFileHandler", + "level": "DEBUG", + "formatter": "simple", + "when": "D", + "backupCount": "1", + "filename": os.path.join(settings.getBaseFolder("logs"), "octoprint.log") + }, + "serialFile": { + "class": "logging.handlers.RotatingFileHandler", + "level": "DEBUG", + "formatter": "simple", + "maxBytes": 2 * 1024 * 1024, # let's limit the serial log to 2MB in size + "filename": os.path.join(settings.getBaseFolder("logs"), "serial.log") + } + }, + "loggers": { + "SERIAL": { + "level": "CRITICAL", + "handlers": ["serialFile"], + "propagate": False + }, + "tornado.application": { + "level": "INFO" + }, + "tornado.general": { + "level": "INFO" + } + }, + "root": { + "level": "INFO", + "handlers": ["console", "file"] + } + } + + if debug: + default_config["root"]["level"] = "DEBUG" + + if use_logging_file: + if logging_file is None: + logging_file = os.path.join(settings.getBaseFolder("base"), "logging.yaml") + + config_from_file = {} + if os.path.exists(logging_file) and os.path.isfile(logging_file): + import yaml + with open(logging_file, "r") as f: + config_from_file = yaml.safe_load(f) + + config = dict_merge(default_config, config_from_file) + else: + config = default_config + + logging.config.dictConfig(config) + logging.captureWarnings(True) + + import warnings + warnings.simplefilter("always") + + if uncaught_logger is None: + logger = logging.getLogger(__name__) + else: + logger = logging.getLogger(uncaught_logger) + + if uncaught_handler is None: + def exception_logger(exc_type, exc_value, exc_tb): + logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_tb)) + uncaught_handler = exception_logger + sys.excepthook = uncaught_handler + + return logger + + +def init_pluginsystem(settings): + from octoprint.plugin import plugin_manager + return plugin_manager(init=True, settings=settings) + +#~~ Custom click option to hide from help + +class HiddenOption(click.Option): + def get_help_record(self, ctx): + pass + +def hidden_option(*param_decls, **attrs): + """Attaches a hidden option to the command. All positional arguments are + passed as parameter declarations to :class:`Option`; all keyword + arguments are forwarded unchanged. This is equivalent to creating an + :class:`Option` instance manually and attaching it to the + :attr:`Command.params` list. + """ + + import inspect + from click.decorators import _param_memo + + def decorator(f): + if 'help' in attrs: + attrs['help'] = inspect.cleandoc(attrs['help']) + _param_memo(f, HiddenOption(param_decls, **attrs)) + return f + return decorator + + +#~~ daemon class + +class OctoPrintDaemon(Daemon): + def __init__(self, pidfile, basedir, configfile, host, port, debug, allow_root, logging_config): Daemon.__init__(self, pidfile) - self._configfile = configfile self._basedir = basedir + self._configfile = configfile self._host = host self._port = port self._debug = debug - self._allowRoot = allowRoot - self._logConf = logConf + self._allow_root = allow_root + self._logging_config = logging_config def run(self): - octoprint = Server(self._configfile, self._basedir, self._host, self._port, self._debug, self._allowRoot) - octoprint.run() + run_server(self._basedir, self._configfile, self._host, self._port, self._debug, self._allow_root, self._logging_config) + +#~~ serve method + +def run_server(basedir, configfile, host, port, debug, allow_root, logging_config): + settings, _, plugin_manager = init_platform(basedir, + configfile, + logging_file=logging_config, + debug=debug, + uncaught_logger=__name__) + + octoprint = Server(settings=settings, plugin_manager=plugin_manager, host=host, port=port, debug=debug, allow_root=allow_root) + octoprint.run() + + +@click.group(name="octoprint", invoke_without_command=True) +@click.option("--basedir", "-b", type=click.Path(), + help="Specify the basedir to use for uploads, timelapses etc.") +@click.option("--config", "-c", "configfile", type=click.Path(), + help="Specify the config file to use.") +@hidden_option("--debug", "-d", is_flag=True) +@hidden_option("--host", type=click.STRING) +@hidden_option("--port", type=click.INT) +@hidden_option("--logging", type=click.Path()) +@hidden_option("--daemon", type=click.Choice(["start", "stop", "restart"])) +@hidden_option("--pid", type=click.Path()) +@hidden_option("--iknowwhatimdoing", "allow_root", is_flag=True) +@click.version_option(version=__version__) +@click.pass_context +def cli(ctx, debug, host, port, basedir, configfile, logging, daemon, pid, allow_root): + class ContextObject(object): + def __init__(self): + self.debug = False + self.configfile = None + self.basedir = None + self.allow_root = None + + obj = ContextObject() + obj.debug = debug + obj.configfile = configfile + obj.basedir = basedir + obj.allow_root = allow_root + + ctx.obj = obj + + if ctx.invoked_subcommand is None: + if daemon: + click.echo("Daemon operation via \"octoprint --daemon " + "(start|stop|restart)\" is deprecated, please use " + "\"octoprint daemon start|stop|restart\" from now on") + + ctx.invoke(daemon_command, pid=pid, daemon=daemon) + else: + click.echo("Starting the server via \"octoprint\" is deprecated, " + "please use \"octoprint serve\" from now on.") + + ctx.invoke(serve_command, host=host, port=port, logging=logging) + + +@cli.command(name="serve") +@click.option("--host", type=click.STRING, + help="Specify the host on which to bind the server.") +@click.option("--port", type=click.INT, + help="Specify the port on which to bind the server.") +@click.option("--logging", type=click.Path(), + help="Specify the config file to use for configuring logging.") +@click.option("--debug", "-d", is_flag=True, + help="Enable debug mode.") +@click.option("--iknowwhatimdoing", "allow_root", is_flag=True, + help="Allow OctoPrint to run as user root.") +@click.pass_context +def serve_command(ctx, host, port, logging, debug, allow_root): + """Starts the OctoPrint server.""" + run_server(ctx.obj.basedir, ctx.obj.configfile, host, port, debug, + allow_root, logging) + + +@cli.command(name="daemon") +@click.option("--pid", type=click.Path(), + help="Pidfile to use for daemonizing.") +@click.option("--host", type=click.STRING, + help="Specify the host on which to bind the server.") +@click.option("--port", type=click.INT, + help="Specify the port on which to bind the server.") +@click.option("--logging", type=click.Path(), + help="Specify the config file to use for configuring logging.") +@click.option("--debug", "-d", is_flag=True, + help="Enable debug mode") +@click.option("--iknowwhatimdoing", "allow_root", is_flag=True, + help="Allow OctoPrint to run as user root.") +@click.argument("command", type=click.Choice(["start", "stop", "restart"]), + metavar="start|stop|restart") +@click.pass_context +def daemon_command(ctx, pid, host, port, logging, debug, allow_root, command): + """ + Starts, stops or restarts in daemon mode. + + Please note that daemon mode is only supported under Linux right now. + """ + if sys.platform == "darwin" or sys.platform == "win32": + click.echo("Sorry, daemon mode is only supported under Linux right now", + file=sys.stderr) + sys.exit(2) + + daemon = OctoPrintDaemon(pid, ctx.obj.basedir, ctx.obj.configfile, + host, port, debug, allow_root, logging) + + if command == "start": + daemon.start() + elif command == "stop": + daemon.stop() + elif command == "restart": + daemon.restart() + def main(): - import argparse - - parser = argparse.ArgumentParser(prog="run") - - parser.add_argument("-v", "--version", action="store_true", dest="version", - help="Output OctoPrint's version and exit") - - parser.add_argument("-d", "--debug", action="store_true", dest="debug", - help="Enable debug mode") - - parser.add_argument("--host", action="store", type=str, dest="host", - help="Specify the host on which to bind the server") - parser.add_argument("--port", action="store", type=int, dest="port", - help="Specify the port on which to bind the server") - - parser.add_argument("-c", "--config", action="store", dest="config", - help="Specify the config file to use. OctoPrint needs to have write access for the settings dialog to work. Defaults to ~/.octoprint/config.yaml") - parser.add_argument("-b", "--basedir", action="store", dest="basedir", - help="Specify the basedir to use for uploads, timelapses etc. OctoPrint needs to have write access. Defaults to ~/.octoprint") - parser.add_argument("--logging", action="store", dest="logConf", - help="Specify the config file to use for configuring logging. Defaults to ~/.octoprint/logging.yaml") - - parser.add_argument("--daemon", action="store", type=str, choices=["start", "stop", "restart"], - help="Daemonize/control daemonized OctoPrint instance (only supported under Linux right now)") - parser.add_argument("--pid", action="store", type=str, dest="pidfile", default="/tmp/octoprint.pid", - help="Pidfile to use for daemonizing, defaults to /tmp/octoprint.pid") - - parser.add_argument("--iknowwhatimdoing", action="store_true", dest="allowRoot", - help="Allow OctoPrint to run as user root") - - args = parser.parse_args() - - if args.version: - print "OctoPrint version %s" % __version__ - sys.exit(0) - - if args.daemon: - if sys.platform == "darwin" or sys.platform == "win32": - print >> sys.stderr, "Sorry, daemon mode is only supported under Linux right now" - sys.exit(2) - - daemon = Main(args.pidfile, args.config, args.basedir, args.host, args.port, args.debug, args.allowRoot, args.logConf) - if "start" == args.daemon: - daemon.start() - elif "stop" == args.daemon: - daemon.stop() - elif "restart" == args.daemon: - daemon.restart() - else: - octoprint = Server(args.config, args.basedir, args.host, args.port, args.debug, args.allowRoot, args.logConf) - octoprint.run() + cli.main(prog_name="octoprint", auto_envvar_prefix="OCTOPRINT") + if __name__ == "__main__": main() diff --git a/src/octoprint/plugin/__init__.py b/src/octoprint/plugin/__init__.py index 21976d08b7..6180a8a6eb 100644 --- a/src/octoprint/plugin/__init__.py +++ b/src/octoprint/plugin/__init__.py @@ -22,7 +22,7 @@ import os import logging -from octoprint.settings import settings +from octoprint.settings import settings as s from octoprint.plugin.core import (PluginInfo, PluginManager, Plugin) from octoprint.plugin.types import * @@ -44,7 +44,7 @@ def _validate_plugin(phase, plugin_info): setattr(plugin_info.instance, PluginInfo.attr_hooks, hooks) def plugin_manager(init=False, plugin_folders=None, plugin_types=None, plugin_entry_points=None, plugin_disabled_list=None, - plugin_restart_needing_hooks=None, plugin_obsolete_hooks=None, plugin_validators=None): + plugin_restart_needing_hooks=None, plugin_obsolete_hooks=None, plugin_validators=None, settings=None): """ Factory method for initially constructing and consecutively retrieving the :class:`~octoprint.plugin.core.PluginManager` singleton. @@ -87,9 +87,12 @@ def plugin_manager(init=False, plugin_folders=None, plugin_types=None, plugin_en else: if init: + if settings is None: + settings = s() + if plugin_folders is None: plugin_folders = ( - settings().getBaseFolder("plugins"), + settings.getBaseFolder("plugins"), (os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "plugins")), True) ) if plugin_types is None: @@ -107,7 +110,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: - plugin_disabled_list = settings().get(["plugins", "_disabled"]) + plugin_disabled_list = settings.get(["plugins", "_disabled"]) if plugin_restart_needing_hooks is None: plugin_restart_needing_hooks = [ "octoprint.server.http" @@ -134,7 +137,7 @@ def plugin_manager(init=False, plugin_folders=None, plugin_types=None, plugin_en return _instance -def plugin_settings(plugin_key, defaults=None, get_preprocessors=None, set_preprocessors=None): +def plugin_settings(plugin_key, defaults=None, get_preprocessors=None, set_preprocessors=None, settings=None): """ Factory method for creating a :class:`PluginSettings` instance. @@ -143,12 +146,17 @@ def plugin_settings(plugin_key, defaults=None, get_preprocessors=None, set_prepr defaults (dict): The default settings for the plugin. get_preprocessors (dict): The getter preprocessors for the plugin. set_preprocessors (dict): The setter preprocessors for the plugin. + settings (octoprint.settings.Settings): The settings instance to use. Returns: PluginSettings: A fully initialized :class:`PluginSettings` instance to be used to access the plugin's settings """ - return PluginSettings(settings(), plugin_key, defaults=defaults, get_preprocessors=get_preprocessors, set_preprocessors=set_preprocessors) + if settings is None: + settings = s() + return PluginSettings(settings, plugin_key, defaults=defaults, + get_preprocessors=get_preprocessors, + set_preprocessors=set_preprocessors) def call_plugin(types, method, args=None, kwargs=None, callback=None, error_callback=None): diff --git a/src/octoprint/plugin/core.py b/src/octoprint/plugin/core.py index 913289de81..31a5bbd5ff 100644 --- a/src/octoprint/plugin/core.py +++ b/src/octoprint/plugin/core.py @@ -468,8 +468,6 @@ def __init__(self, plugin_folders, plugin_types, plugin_entry_points, logging_pr self.marked_plugins = defaultdict(list) - self.reload_plugins(startup=True, initialize_implementations=False) - @property def plugins(self): plugins = dict(self.enabled_plugins) @@ -955,16 +953,14 @@ def log_all_plugins(self, show_bundled=True, bundled_str=(" (bundled)", ""), sho self.logger.info("No plugins available") else: self.logger.info("{count} plugin(s) registered with the system:\n{plugins}".format(count=len(all_plugins), plugins="\n".join( - sorted( - map(lambda x: "| " + x.long_str(show_bundled=show_bundled, - bundled_strs=bundled_str, - show_location=show_location, - location_str=location_str, - show_enabled=show_enabled, - enabled_strs=enabled_str), - self.enabled_plugins.values()) - ) - ))) + map(lambda x: "| " + x.long_str(show_bundled=show_bundled, + bundled_strs=bundled_str, + show_location=show_location, + location_str=location_str, + show_enabled=show_enabled, + enabled_strs=enabled_str), + sorted(self.enabled_plugins.values(), key=lambda x: x.name.lower()), + )))) def get_plugin(self, identifier, require_enabled=True): """ diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index b70655cf10..7240f4724e 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -108,14 +108,13 @@ def load_user(id): class Server(): - def __init__(self, configfile=None, basedir=None, host="0.0.0.0", port=5000, debug=False, allowRoot=False, logConf=None): - self._configfile = configfile - self._basedir = basedir + def __init__(self, settings=None, plugin_manager=None, host="0.0.0.0", port=5000, debug=False, allow_root=False): + self._settings = settings + self._plugin_manager = plugin_manager self._host = host self._port = port self._debug = debug - self._allowRoot = allowRoot - self._logConf = logConf + self._allow_root = allow_root self._server = None self._logger = None @@ -125,9 +124,14 @@ def __init__(self, configfile=None, basedir=None, host="0.0.0.0", port=5000, deb self._template_searchpaths = [] def run(self): - if not self._allowRoot: + if not self._allow_root: self._check_for_root() + if self._settings is None: + self._settings = settings() + if self._plugin_manager is None: + self._plugin_manager = octoprint.plugin.plugin_manager() + global app global babel @@ -147,16 +151,16 @@ def run(self): from tornado.ioloop import IOLoop from tornado.web import Application, RequestHandler - import sys - debug = self._debug - # first initialize the settings singleton and make sure it uses given configfile and basedir if available - s = settings(init=True, basedir=self._basedir, configfile=self._configfile) + self._logger = logging.getLogger(__name__) + pluginManager = self._plugin_manager + + self._logger.info("Starting OctoPrint %s" % DISPLAY_VERSION) - # then monkey patch a bunch of stuff + # monkey patch a bunch of stuff util.tornado.fix_ioloop_scheduling() - util.flask.enable_additional_translations(additional_folders=[s.getBaseFolder("translations")]) + util.flask.enable_additional_translations(additional_folders=[self._settings.getBaseFolder("translations")]) # setup app self._setup_app() @@ -164,23 +168,20 @@ def run(self): # setup i18n self._setup_i18n(app) - # then initialize logging - self._setup_logging(self._debug, self._logConf) - self._logger = logging.getLogger(__name__) - def exception_logger(exc_type, exc_value, exc_tb): - self._logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_tb)) - sys.excepthook = exception_logger - self._logger.info("Starting OctoPrint %s" % DISPLAY_VERSION) + if self._settings.getBoolean(["serial", "log"]): + # enable debug logging to serial.log + logging.getLogger("SERIAL").setLevel(logging.DEBUG) + logging.getLogger("SERIAL").debug("Enabling serial logging") - # then initialize the plugin manager - pluginManager = octoprint.plugin.plugin_manager(init=True) + # load plugins + pluginManager.reload_plugins(startup=True, initialize_implementations=False) printerProfileManager = PrinterProfileManager() eventManager = events.eventManager() analysisQueue = octoprint.filemanager.analysis.AnalysisQueue() - slicingManager = octoprint.slicing.SlicingManager(s.getBaseFolder("slicingProfiles"), printerProfileManager) + slicingManager = octoprint.slicing.SlicingManager(self._settings.getBaseFolder("slicingProfiles"), printerProfileManager) storage_managers = dict() - storage_managers[octoprint.filemanager.FileDestinations.LOCAL] = octoprint.filemanager.storage.LocalFileStorage(s.getBaseFolder("uploads")) + storage_managers[octoprint.filemanager.FileDestinations.LOCAL] = octoprint.filemanager.storage.LocalFileStorage(self._settings.getBaseFolder("uploads")) fileManager = octoprint.filemanager.FileManager(analysisQueue, slicingManager, printerProfileManager, initial_storage_managers=storage_managers) printer = Printer(fileManager, analysisQueue, printerProfileManager) appSessionManager = util.flask.AppSessionManager() @@ -199,7 +200,7 @@ def octoprint_plugin_inject_factory(name, implementation): printer=printer, app_session_manager=appSessionManager, plugin_lifecycle_manager=pluginLifecycleManager, - data_folder=os.path.join(settings().getBaseFolder("data"), name) + data_folder=os.path.join(self._settings.getBaseFolder("data"), name) ) def settings_plugin_inject_factory(name, implementation): @@ -276,8 +277,8 @@ def template_disabled(name, plugin): events.DebugEventListener() # setup access control - if s.getBoolean(["accessControl", "enabled"]): - userManagerName = s.get(["accessControl", "userManager"]) + if self._settings.getBoolean(["accessControl", "enabled"]): + userManagerName = self._settings.get(["accessControl", "userManager"]) try: clazz = octoprint.util.get_class(userManagerName) userManager = clazz() @@ -286,22 +287,22 @@ def template_disabled(name, plugin): app.wsgi_app = util.ReverseProxied( app.wsgi_app, - s.get(["server", "reverseProxy", "prefixHeader"]), - s.get(["server", "reverseProxy", "schemeHeader"]), - s.get(["server", "reverseProxy", "hostHeader"]), - s.get(["server", "reverseProxy", "prefixFallback"]), - s.get(["server", "reverseProxy", "schemeFallback"]), - s.get(["server", "reverseProxy", "hostFallback"]) + self._settings.get(["server", "reverseProxy", "prefixHeader"]), + self._settings.get(["server", "reverseProxy", "schemeHeader"]), + self._settings.get(["server", "reverseProxy", "hostHeader"]), + self._settings.get(["server", "reverseProxy", "prefixFallback"]), + self._settings.get(["server", "reverseProxy", "schemeFallback"]), + self._settings.get(["server", "reverseProxy", "hostFallback"]) ) - secret_key = s.get(["server", "secretKey"]) + secret_key = self._settings.get(["server", "secretKey"]) if not secret_key: import string from random import choice chars = string.ascii_lowercase + string.ascii_uppercase + string.digits secret_key = "".join(choice(chars) for _ in xrange(32)) - s.set(["server", "secretKey"], secret_key) - s.save() + self._settings.set(["server", "secretKey"], secret_key) + self._settings.save() app.secret_key = secret_key loginManager = LoginManager() loginManager.session_protection = "strong" @@ -312,9 +313,9 @@ def template_disabled(name, plugin): loginManager.init_app(app) if self._host is None: - self._host = s.get(["server", "host"]) + self._host = self._settings.get(["server", "host"]) if self._port is None: - self._port = s.getInt(["server", "port"]) + self._port = self._settings.getInt(["server", "port"]) app.debug = self._debug @@ -328,17 +329,17 @@ def template_disabled(name, plugin): self._router = SockJSRouter(self._create_socket_connection, "/sockjs") - upload_suffixes = dict(name=s.get(["server", "uploads", "nameSuffix"]), path=s.get(["server", "uploads", "pathSuffix"])) + upload_suffixes = dict(name=self._settings.get(["server", "uploads", "nameSuffix"]), path=self._settings.get(["server", "uploads", "pathSuffix"])) server_routes = self._router.urls + [ # various downloads - (r"/downloads/timelapse/([^/]*\.mpg)", util.tornado.LargeResponseHandler, dict(path=s.getBaseFolder("timelapse"), as_attachment=True)), - (r"/downloads/files/local/(.*)", util.tornado.LargeResponseHandler, dict(path=s.getBaseFolder("uploads"), as_attachment=True, path_validation=util.tornado.path_validation_factory(lambda path: not os.path.basename(path).startswith("."), status_code=404))), - (r"/downloads/logs/([^/]*)", util.tornado.LargeResponseHandler, dict(path=s.getBaseFolder("logs"), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.admin_validator))), + (r"/downloads/timelapse/([^/]*\.mpg)", util.tornado.LargeResponseHandler, dict(path=self._settings.getBaseFolder("timelapse"), as_attachment=True)), + (r"/downloads/files/local/(.*)", util.tornado.LargeResponseHandler, dict(path=self._settings.getBaseFolder("uploads"), as_attachment=True, path_validation=util.tornado.path_validation_factory(lambda path: not os.path.basename(path).startswith("."), status_code=404))), + (r"/downloads/logs/([^/]*)", util.tornado.LargeResponseHandler, dict(path=self._settings.getBaseFolder("logs"), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.admin_validator))), # camera snapshot - (r"/downloads/camera/current", util.tornado.UrlForwardHandler, dict(url=s.get(["webcam", "snapshot"]), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.user_validator))), + (r"/downloads/camera/current", util.tornado.UrlForwardHandler, dict(url=self._settings.get(["webcam", "snapshot"]), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.user_validator))), # generated webassets - (r"/static/webassets/(.*)", util.tornado.LargeResponseHandler, dict(path=os.path.join(s.getBaseFolder("generated"), "webassets"))) + (r"/static/webassets/(.*)", util.tornado.LargeResponseHandler, dict(path=os.path.join(self._settings.getBaseFolder("generated"), "webassets"))) ] for name, hook in pluginManager.get_hooks("octoprint.server.http.routes").items(): try: @@ -365,7 +366,7 @@ def template_disabled(name, plugin): self._tornado_app = Application(server_routes) max_body_sizes = [ - ("POST", r"/api/files/([^/]*)", s.getInt(["server", "uploads", "maxSize"])), + ("POST", r"/api/files/([^/]*)", self._settings.getInt(["server", "uploads", "maxSize"])), ("POST", r"/api/languages", 5 * 1024 * 1024) ] @@ -391,25 +392,25 @@ def template_disabled(name, plugin): self._logger.debug("Adding maximum body size of {size}B for {method} requests to {route})".format(**locals())) max_body_sizes.append((method, route, size)) - self._server = util.tornado.CustomHTTPServer(self._tornado_app, max_body_sizes=max_body_sizes, default_max_body_size=s.getInt(["server", "maxSize"])) + self._server = util.tornado.CustomHTTPServer(self._tornado_app, max_body_sizes=max_body_sizes, default_max_body_size=self._settings.getInt(["server", "maxSize"])) self._server.listen(self._port, address=self._host) eventManager.fire(events.Events.STARTUP) - if s.getBoolean(["serial", "autoconnect"]): - (port, baudrate) = s.get(["serial", "port"]), s.getInt(["serial", "baudrate"]) + if self._settings.getBoolean(["serial", "autoconnect"]): + (port, baudrate) = self._settings.get(["serial", "port"]), self._settings.getInt(["serial", "baudrate"]) printer_profile = printerProfileManager.get_default() connectionOptions = get_connection_options() if port in connectionOptions["ports"]: printer.connect(port=port, baudrate=baudrate, profile=printer_profile["id"] if "id" in printer_profile else "_default") # start up watchdogs - if s.getBoolean(["feature", "pollWatched"]): + if self._settings.getBoolean(["feature", "pollWatched"]): # use less performant polling observer if explicitely configured observer = PollingObserver() else: # use os default observer = Observer() - observer.schedule(util.watchdog.GcodeWatchdogHandler(fileManager, printer), s.getBaseFolder("watched")) + observer.schedule(util.watchdog.GcodeWatchdogHandler(fileManager, printer), self._settings.getBaseFolder("watched")) observer.start() # run our startup plugins @@ -499,89 +500,12 @@ def _get_locale(self): except octoprint.users.UnknownUser: pass - default_language = settings().get(["appearance", "defaultLanguage"]) + default_language = self._settings.get(["appearance", "defaultLanguage"]) if default_language is not None and not default_language == "_default" and default_language in LANGUAGES: return Locale.negotiate([default_language], LANGUAGES) return request.accept_languages.best_match(LANGUAGES) - def _setup_logging(self, debug, logConf=None): - defaultConfig = { - "version": 1, - "formatters": { - "simple": { - "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - } - }, - "handlers": { - "console": { - "class": "logging.StreamHandler", - "level": "DEBUG", - "formatter": "simple", - "stream": "ext://sys.stdout" - }, - "file": { - "class": "logging.handlers.TimedRotatingFileHandler", - "level": "DEBUG", - "formatter": "simple", - "when": "D", - "backupCount": "1", - "filename": os.path.join(settings().getBaseFolder("logs"), "octoprint.log") - }, - "serialFile": { - "class": "logging.handlers.RotatingFileHandler", - "level": "DEBUG", - "formatter": "simple", - "maxBytes": 2 * 1024 * 1024, # let's limit the serial log to 2MB in size - "filename": os.path.join(settings().getBaseFolder("logs"), "serial.log") - } - }, - "loggers": { - "SERIAL": { - "level": "CRITICAL", - "handlers": ["serialFile"], - "propagate": False - }, - "tornado.application": { - "level": "INFO" - }, - "tornado.general": { - "level": "INFO" - }, - "octoprint.server.util.flask": { - "level": "WARN" - } - }, - "root": { - "level": "INFO", - "handlers": ["console", "file"] - } - } - - if debug: - defaultConfig["root"]["level"] = "DEBUG" - - if logConf is None: - logConf = os.path.join(settings().getBaseFolder("base"), "logging.yaml") - - configFromFile = {} - if os.path.exists(logConf) and os.path.isfile(logConf): - import yaml - with open(logConf, "r") as f: - configFromFile = yaml.safe_load(f) - - config = octoprint.util.dict_merge(defaultConfig, configFromFile) - logging.config.dictConfig(config) - logging.captureWarnings(True) - - import warnings - warnings.simplefilter("always") - - if settings().getBoolean(["serial", "log"]): - # enable debug logging to serial.log - logging.getLogger("SERIAL").setLevel(logging.DEBUG) - logging.getLogger("SERIAL").debug("Enabling serial logging") - def _setup_app(self): @app.before_request def before_request(): @@ -731,10 +655,10 @@ def _setup_assets(self): util.flask.fix_webassets_cache() util.flask.fix_webassets_filtertool() - base_folder = settings().getBaseFolder("generated") + base_folder = self._settings.getBaseFolder("generated") # clean the folder - if settings().getBoolean(["devel", "webassets", "clean_on_startup"]): + if self._settings.getBoolean(["devel", "webassets", "clean_on_startup"]): import shutil for entry in ("webassets", ".webassets-cache"): path = os.path.join(base_folder, entry) @@ -756,16 +680,16 @@ def directory(self): return base_folder assets = CustomDirectoryEnvironment(app) - assets.debug = not settings().getBoolean(["devel", "webassets", "bundle"]) + assets.debug = not self._settings.getBoolean(["devel", "webassets", "bundle"]) UpdaterType = type(util.flask.SettingsCheckUpdater)(util.flask.SettingsCheckUpdater.__name__, (util.flask.SettingsCheckUpdater,), dict( updater=assets.updater )) assets.updater = UpdaterType - enable_gcodeviewer = settings().getBoolean(["gcodeViewer", "enabled"]) - enable_timelapse = (settings().get(["webcam", "snapshot"]) and settings().get(["webcam", "ffmpeg"])) - preferred_stylesheet = settings().get(["devel", "stylesheet"]) + enable_gcodeviewer = self._settings.getBoolean(["gcodeViewer", "enabled"]) + enable_timelapse = (self._settings.get(["webcam", "snapshot"]) and self._settings.get(["webcam", "ffmpeg"])) + preferred_stylesheet = self._settings.get(["devel", "stylesheet"]) dynamic_assets = util.flask.collect_plugin_assets( enable_gcodeviewer=enable_gcodeviewer, @@ -857,7 +781,7 @@ def input(self, _in, out, **kwargs): register_filter(JsDelimiterBundle) js_libs_bundle = Bundle(*js_libs, output="webassets/packed_libs.js", filters="js_delimiter_bundler") - if settings().getBoolean(["devel", "webassets", "minify"]): + if self._settings.getBoolean(["devel", "webassets", "minify"]): js_app_bundle = Bundle(*js_app, output="webassets/packed_app.js", filters="rjsmin, js_delimiter_bundler") else: js_app_bundle = Bundle(*js_app, output="webassets/packed_app.js", filters="js_delimiter_bundler")