Better command line interface
This commit is contained in:
parent
75992ef837
commit
a65ad50898
7 changed files with 387 additions and 219 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
3
setup.py
3
setup.py
|
|
@ -31,7 +31,8 @@ INSTALL_REQUIRES = [
|
|||
"rsa",
|
||||
"pkginfo",
|
||||
"requests",
|
||||
"semantic_version"
|
||||
"semantic_version",
|
||||
"Click"
|
||||
]
|
||||
|
||||
# Additional requirements for optional install options
|
||||
|
|
|
|||
|
|
@ -1,5 +1,14 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
__author__ = "Gina Häußge <osd@foosel.net>"
|
||||
__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 @@ from ._version import get_versions
|
|||
__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
|
||||
cli.main(prog_name="octoprint", auto_envvar_prefix="OCTOPRINT")
|
||||
|
||||
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()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms
|
|||
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):
|
||||
|
|
|
|||
|
|
@ -468,8 +468,6 @@ class PluginManager(object):
|
|||
|
||||
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 @@ class PluginManager(object):
|
|||
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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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 @@ class Server():
|
|||
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 @@ class Server():
|
|||
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
|
||||
|
||||
# then monkey patch a bunch of stuff
|
||||
self._logger.info("Starting OctoPrint %s" % DISPLAY_VERSION)
|
||||
|
||||
# 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 @@ class Server():
|
|||
# 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 @@ class Server():
|
|||
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 @@ class Server():
|
|||
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 @@ class Server():
|
|||
|
||||
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 @@ class Server():
|
|||
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 @@ class Server():
|
|||
|
||||
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 @@ class Server():
|
|||
|
||||
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 @@ class Server():
|
|||
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 @@ class Server():
|
|||
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 @@ class Server():
|
|||
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 @@ class Server():
|
|||
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 @@ class Server():
|
|||
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")
|
||||
|
|
|
|||
Loading…
Reference in a new issue