MrDraw/src/octoprint/server/__init__.py
2015-02-27 20:28:24 +01:00

808 lines
29 KiB
Python

# coding=utf-8
from __future__ import absolute_import
__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
import uuid
from sockjs.tornado import SockJSRouter
from flask import Flask, render_template, send_from_directory, g, request, make_response, session, url_for
from flask.ext.login import LoginManager
from flask.ext.principal import Principal, Permission, RoleNeed, identity_loaded, UserNeed
from flask.ext.babel import Babel, gettext, ngettext
from babel import Locale
from watchdog.observers import Observer
from collections import defaultdict
import os
import logging
import logging.config
import atexit
SUCCESS = {}
NO_CONTENT = ("", 204)
app = Flask("octoprint")
babel = Babel(app)
debug = False
printer = None
printerProfileManager = None
fileManager = None
slicingManager = None
analysisQueue = None
userManager = None
eventManager = None
loginManager = None
pluginManager = None
appSessionManager = None
principals = Principal(app)
admin_permission = Permission(RoleNeed("admin"))
user_permission = Permission(RoleNeed("user"))
# only import the octoprint stuff down here, as it might depend on things defined above to be initialized already
from octoprint.printer import Printer, getConnectionOptions
from octoprint.printer.profile import PrinterProfileManager
from octoprint.settings import settings
import octoprint.users as users
import octoprint.events as events
import octoprint.plugin
import octoprint.timelapse
import octoprint._version
import octoprint.util
import octoprint.filemanager.storage
import octoprint.filemanager.analysis
import octoprint.slicing
from . import util
UI_API_KEY = ''.join('%02X' % ord(z) for z in uuid.uuid4().bytes)
versions = octoprint._version.get_versions()
VERSION = versions['version']
BRANCH = versions['branch'] if 'branch' in versions else None
DISPLAY_VERSION = "%s (%s branch)" % (VERSION, BRANCH) if BRANCH else VERSION
del versions
def get_available_locale_identifiers(locales):
result = set()
# add available translations
for locale in locales:
result.add(locale.language)
if locale.territory:
# if a territory is specified, add that too
result.add("%s_%s" % (locale.language, locale.territory))
return result
LOCALES = [Locale.parse("en")] + babel.list_translations()
LANGUAGES = get_available_locale_identifiers(LOCALES)
@app.before_request
def before_request():
g.locale = get_locale()
@app.after_request
def after_request(response):
# send no-cache headers with all POST responses
if request.method == "POST":
response.cache_control.no_cache = True
return response
@babel.localeselector
def get_locale():
if "l10n" in request.values:
return Locale.negotiate([request.values["l10n"]], LANGUAGES)
return request.accept_languages.best_match(LANGUAGES)
@app.route("/")
@util.flask.cached(refreshif=lambda: util.flask.cache_check_headers() or "_refresh" in request.values)
def index():
#~~ a bunch of settings
enable_gcodeviewer = settings().getBoolean(["gcodeViewer", "enabled"])
enable_timelapse = (settings().get(["webcam", "snapshot"]) and settings().get(["webcam", "ffmpeg"]))
enable_systemmenu = settings().get(["system"]) is not None and settings().get(["system", "actions"]) is not None and len(settings().get(["system", "actions"])) > 0
enable_accesscontrol = userManager is not None
preferred_stylesheet = settings().get(["devel", "stylesheet"])
#~~ prepare assets
supported_stylesheets = ("css", "less")
assets = dict(
js=[],
stylesheets=[]
)
assets["js"] = [
url_for('static', filename='js/app/viewmodels/appearance.js'),
url_for('static', filename='js/app/viewmodels/connection.js'),
url_for('static', filename='js/app/viewmodels/control.js'),
url_for('static', filename='js/app/viewmodels/firstrun.js'),
url_for('static', filename='js/app/viewmodels/files.js'),
url_for('static', filename='js/app/viewmodels/loginstate.js'),
url_for('static', filename='js/app/viewmodels/navigation.js'),
url_for('static', filename='js/app/viewmodels/printerstate.js'),
url_for('static', filename='js/app/viewmodels/printerprofiles.js'),
url_for('static', filename='js/app/viewmodels/settings.js'),
url_for('static', filename='js/app/viewmodels/slicing.js'),
url_for('static', filename='js/app/viewmodels/temperature.js'),
url_for('static', filename='js/app/viewmodels/terminal.js'),
url_for('static', filename='js/app/viewmodels/users.js'),
url_for('static', filename='js/app/viewmodels/log.js')
]
if enable_gcodeviewer:
assets["js"] += [
url_for('static', filename='js/app/viewmodels/gcode.js'),
url_for('static', filename='gcodeviewer/js/ui.js'),
url_for('static', filename='gcodeviewer/js/gCodeReader.js'),
url_for('static', filename='gcodeviewer/js/renderer.js')
]
if enable_timelapse:
assets["js"].append(url_for('static', filename='js/app/viewmodels/timelapse.js'))
if preferred_stylesheet == "less":
assets["stylesheets"].append(("less", url_for('static', filename='less/octoprint.less')))
elif preferred_stylesheet == "css":
assets["stylesheets"].append(("css", url_for('static', filename='css/octoprint.css')))
asset_plugins = pluginManager.get_implementations(octoprint.plugin.AssetPlugin)
for name, implementation in asset_plugins.items():
all_assets = implementation.get_assets()
if "js" in all_assets:
for asset in all_assets["js"]:
assets["js"].append(url_for('plugin_assets', name=name, filename=asset))
if preferred_stylesheet in all_assets:
for asset in all_assets[preferred_stylesheet]:
assets["stylesheets"].append((preferred_stylesheet, url_for('plugin_assets', name=name, filename=asset)))
else:
for stylesheet in supported_stylesheets:
if not stylesheet in all_assets:
continue
for asset in all_assets[stylesheet]:
assets["stylesheets"].append((stylesheet, url_for('plugin_assets', name=name, filename=asset)))
break
##~~ prepare templates
templates = dict(
navbar=dict(order=[], entries=dict()),
sidebar=dict(order=[], entries=dict()),
tab=dict(order=[], entries=dict()),
settings=dict(order=[], entries=dict()),
generic=dict(order=[], entries=dict())
)
# navbar
templates["navbar"]["entries"] = dict(
settings=dict(template="navbar/settings.jinja2", _div="navbar_settings", styles=["display: none"], data_bind="visible: loginState.isAdmin")
)
if enable_accesscontrol:
templates["navbar"]["entries"]["login"] = dict(template="navbar/login.jinja2", _div="navbar_login", classes=["dropdown"], custom_bindings=False)
if enable_systemmenu:
templates["navbar"]["entries"]["systemmenu"] = dict(template="navbar/systemmenu.jinja2", _div="navbar_systemmenu", styles=["display: none"], classes=["dropdown"], data_bind="visible: loginState.isAdmin", custom_bindings=False)
# sidebar
templates["sidebar"]["entries"]= dict(
connection=(gettext("Connection"), dict(template="sidebar/connection.jinja2", _div="connection", icon="signal", styles_wrapper=["display: none"], data_bind="visible: loginState.isAdmin")),
state=(gettext("State"), dict(template="sidebar/state.jinja2", _div="state", icon="info-sign")),
files=(gettext("Files"), dict(template="sidebar/files.jinja2", _div="files", icon="list", classes_content=["overflow_visible"], header_addon="sidebar/files_header.jinja2"))
)
# tabs
templates["tab"]["entries"] = dict(
temperature=(gettext("Temperature"), dict(template="tabs/temperature.jinja2", _div="temp")),
control=(gettext("Control"), dict(template="tabs/control.jinja2", _div="control")),
terminal=(gettext("Terminal"), dict(template="tabs/terminal.jinja2", _div="term")),
)
if enable_gcodeviewer:
templates["tab"]["entries"]["gcodeviewer"] = (gettext("GCode Viewer"), dict(template="tabs/gcodeviewer.jinja2", _div="gcode"))
if enable_timelapse:
templates["tab"]["entries"]["timelapse"] = (gettext("Timelapse"), dict(template="tabs/timelapse.jinja2", _div="timelapse"))
# settings dialog
templates["settings"]["entries"] = dict(
section_printer=(gettext("Printer"), None),
serial=(gettext("Serial Connection"), dict(template="dialogs/settings/serialconnection.jinja2", _div="settings_serialConnection", custom_bindings=False)),
printerprofiles=(gettext("Printer Profiles"), dict(template="dialogs/settings/printerprofiles.jinja2", _div="settings_printerProfiles", custom_bindings=False)),
temperatures=(gettext("Temperatures"), dict(template="dialogs/settings/temperatures.jinja2", _div="settings_temperature", custom_bindings=False)),
terminalfilters=(gettext("Terminal Filters"), dict(template="dialogs/settings/terminalfilters.jinja2", _div="settings_terminalFilters", custom_bindings=False)),
section_features=(gettext("Features"), None),
features=(gettext("Features"), dict(template="dialogs/settings/features.jinja2", _div="settings_features", custom_bindings=False)),
webcam=(gettext("Webcam"), dict(template="dialogs/settings/webcam.jinja2", _div="settings_webcam", custom_bindings=False)),
api=(gettext("API"), dict(template="dialogs/settings/api.jinja2", _div="settings_api", custom_bindings=False)),
section_octoprint=(gettext("OctoPrint"), None),
folders=(gettext("Folders"), dict(template="dialogs/settings/folders.jinja2", _div="settings_folders", custom_bindings=False)),
appearance=(gettext("Appearance"), dict(template="dialogs/settings/appearance.jinja2", _div="settings_appearance", custom_bindings=False)),
logs=(gettext("Logs"), dict(template="dialogs/settings/logs.jinja2", _div="settings_logs")),
)
if enable_accesscontrol:
templates["settings"]["entries"]["accesscontrol"] = (gettext("Access Control"), dict(template="dialogs/settings/accesscontrol.jinja2", _div="settings_users", custom_bindings=False))
# extract data from template plugins
template_plugins = pluginManager.get_implementations(octoprint.plugin.TemplatePlugin)
# rules for transforming template configs to template entries
rules = dict(
navbar=dict(div=lambda x: "navbar_plugin_" + x, template=lambda x: x + "_navbar.jinja2", to_entry=lambda data: data),
sidebar=dict(div=lambda x: "sidebar_plugin_" + x, template=lambda x: x + "_sidebar.jinja2", to_entry=lambda data: (data["name"], data)),
tab=dict(div=lambda x: "tab_plugin_" + x, template=lambda x: x + "_tab.jinja2", to_entry=lambda data: (data["name"], data)),
settings=dict(div=lambda x: "settings_plugin_" + x, template=lambda x: x + "_settings.jinja2", to_entry=lambda data: (data["name"], data)),
generic=dict(template=lambda x: x + ".jinja2", to_entry=lambda data: data)
)
plugin_vars = dict()
plugin_names = template_plugins.keys()
for name, implementation in template_plugins.items():
vars = implementation.get_template_vars()
if not isinstance(vars, dict):
vars = dict()
for var_name, var_value in vars.items():
plugin_vars["plugin_" + name + "_" + var_name] = var_value
configs = implementation.get_template_configs()
if not isinstance(configs, (list, tuple)):
configs = []
includes = _process_template_configs(name, implementation, configs, rules)
for t in ("navbar", "sidebar", "tab", "settings", "generic"):
for include in includes[t]:
if t == "navbar" or t == "generic":
data = include
else:
data = include[1]
key = "plugin_" + name + data["suffix"] if "suffix" in data else ""
if "replaces" in data:
key = data["replaces"]
templates[t]["entries"][key] = include
#~~ order internal templates and plugins
templates["navbar"]["order"] = ["settings", "systemmenu", "login"]
templates["sidebar"]["order"] = ["connection", "state", "files"]
templates["tab"]["order"] = ["temperature", "control", "gcodeviewer", "terminal", "timelapse"]
templates["settings"]["order"] = [
"section_printer", "serial", "printerprofiles", "temperatures", "terminalfilters",
"section_features", "features", "webcam", "accesscontrol", "api",
"section_octoprint", "folders", "appearance", "logs"
]
# make sure that
# 1) we only have keys in our ordered list that we have entries for and
# 2) we have all entries located somewhere within the order
for t in ("navbar", "sidebar", "tab", "settings", "generic"):
templates[t]["order"] = [x for x in templates[t]["order"] if x in templates[t]["entries"]]
all_ordered = set(templates[t]["order"])
missing_in_order = set(templates[t]["entries"].keys()).difference(all_ordered)
if len(missing_in_order) == 0:
continue
sorted_missing = list(missing_in_order)
if not t == "navbar" and not t == "generic":
sorted_missing = sorted(missing_in_order, key=lambda x: templates[t]["entries"][x][0])
if t == "navbar":
templates[t]["order"] = sorted_missing + templates[t]["order"]
elif t == "sidebar" or t == "tab" or t == "generic":
templates[t]["order"] += sorted_missing
elif t == "settings":
templates[t]["entries"]["section_plugins"] = (gettext("Plugins"), None)
templates[t]["order"] += ["section_plugins"] + sorted_missing
#~~ prepare full set of template vars for rendering
render_kwargs = dict(
webcamStream=settings().get(["webcam", "stream"]),
enableTemperatureGraph=settings().get(["feature", "temperatureGraph"]),
enableAccessControl=userManager is not None,
enableSdSupport=settings().get(["feature", "sdSupport"]),
firstRun=settings().getBoolean(["server", "firstRun"]) and (userManager is None or not userManager.hasBeenCustomized()),
debug=debug,
version=VERSION,
display_version=DISPLAY_VERSION,
gcodeMobileThreshold=settings().get(["gcodeViewer", "mobileSizeThreshold"]),
gcodeThreshold=settings().get(["gcodeViewer", "sizeThreshold"]),
uiApiKey=UI_API_KEY,
templates=templates,
assets=assets,
pluginNames=plugin_names
)
render_kwargs.update(plugin_vars)
#~~ render!
return render_template(
"index.jinja2",
**render_kwargs
)
def _process_template_configs(name, implementation, configs, rules):
from jinja2.exceptions import TemplateNotFound
counters = dict(
navbar=1,
sidebar=1,
tab=1,
settings=1,
generic=1
)
includes = defaultdict(list)
for config in configs:
if not isinstance(config, dict):
continue
if not "type" in config:
continue
template_type = config["type"]
del config["type"]
if not template_type in rules:
continue
rule = rules[template_type]
data = _process_template_config(name, implementation, rule, config=config, counter=counters[template_type])
if data is None:
continue
includes[template_type].append(rule["to_entry"](data))
counters[template_type] += 1
for template_type in rules:
if len(includes[template_type]) == 0:
# if no template of that type was added by the config, we'll try to use the default template name
rule = rules[template_type]
data = _process_template_config(name, implementation, rule)
if data is not None:
try:
app.jinja_env.get_or_select_template(data["template"])
except TemplateNotFound:
pass
else:
includes[template_type].append(rule["to_entry"](data))
return includes
def _process_template_config(name, implementation, rule, config=None, counter=1):
if "mandatory" in rule:
for mandatory in rule["mandatory"]:
if not mandatory in config:
return None
if config is None:
config = dict()
data = dict(config)
if "div" in data:
data["_div"] = data["div"]
elif "div" in rule:
data["_div"] = rule["div"](name)
if "suffix" in data:
data["_div"] += "_" + data["suffix"]
elif counter > 1:
data["_div"] += "_%d" % counter
data["suffix"] = "_%d" % counter
else:
data["suffix"] = ""
if not "template" in data:
data["template"] = rule["template"](name)
if not "name" in data:
data["name"] = implementation._plugin_name
if not "custom_bindings" in data or data["custom_bindings"]:
data_bind = "allowBindings: true"
if "data_bind" in data:
data_bind = data_bind + ", " + data["data_bind"]
data["data_bind"] = data_bind
return data
@app.route("/robots.txt")
def robotsTxt():
return send_from_directory(app.static_folder, "robots.txt")
@app.route("/plugin_assets/<string:name>/<path:filename>")
def plugin_assets(name, filename):
asset_plugins = pluginManager.get_implementations(octoprint.plugin.AssetPlugin)
if not name in asset_plugins:
return make_response("Asset not found", 404)
asset_plugin = asset_plugins[name]
asset_folder = asset_plugin.get_asset_folder()
if asset_folder is None:
return make_response("Asset not found", 404)
return send_from_directory(asset_folder, filename)
@identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
user = load_user(identity.id)
if user is None:
return
identity.provides.add(UserNeed(user.get_name()))
if user.is_user():
identity.provides.add(RoleNeed("user"))
if user.is_admin():
identity.provides.add(RoleNeed("admin"))
def load_user(id):
if id == "_api":
return users.ApiUser()
if session and "usersession.id" in session:
sessionid = session["usersession.id"]
else:
sessionid = None
if userManager is not None:
if sessionid:
return userManager.findUser(username=id, session=sessionid)
else:
return userManager.findUser(username=id)
return users.DummyUser()
#~~ startup code
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
self._host = host
self._port = port
self._debug = debug
self._allowRoot = allowRoot
self._logConf = logConf
self._server = None
def run(self):
if not self._allowRoot:
self._checkForRoot()
global printer
global printerProfileManager
global fileManager
global slicingManager
global analysisQueue
global userManager
global eventManager
global loginManager
global pluginManager
global appSessionManager
global debug
from tornado.ioloop import IOLoop
from tornado.web import Application
import sys
debug = self._debug
# first initialize the settings singleton and make sure it uses given configfile and basedir if available
self._initSettings(self._configfile, self._basedir)
# then initialize logging
self._initLogging(self._debug, self._logConf)
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))
sys.excepthook = exception_logger
logger.info("Starting OctoPrint %s" % DISPLAY_VERSION)
# then initialize the plugin manager
pluginManager = octoprint.plugin.plugin_manager(init=True)
printerProfileManager = PrinterProfileManager()
eventManager = events.eventManager()
analysisQueue = octoprint.filemanager.analysis.AnalysisQueue()
slicingManager = octoprint.slicing.SlicingManager(settings().getBaseFolder("slicingProfiles"), printerProfileManager)
storage_managers = dict()
storage_managers[octoprint.filemanager.FileDestinations.LOCAL] = octoprint.filemanager.storage.LocalFileStorage(settings().getBaseFolder("uploads"))
fileManager = octoprint.filemanager.FileManager(analysisQueue, slicingManager, printerProfileManager, initial_storage_managers=storage_managers)
printer = Printer(fileManager, analysisQueue, printerProfileManager)
appSessionManager = util.flask.AppSessionManager()
def octoprint_plugin_inject_factory(name, implementation):
if not isinstance(implementation, octoprint.plugin.OctoPrintPlugin):
return None
return dict(
plugin_manager=pluginManager,
printer_profile_manager=printerProfileManager,
event_bus=eventManager,
analysis_queue=analysisQueue,
slicing_manager=slicingManager,
file_manager=fileManager,
printer=printer,
app_session_manager=appSessionManager
)
def settings_plugin_inject_factory(name, implementation):
if not isinstance(implementation, octoprint.plugin.SettingsPlugin):
return None
default_settings = implementation.get_settings_defaults()
plugin_settings = octoprint.plugin.plugin_settings(name, defaults=default_settings)
return dict(settings=plugin_settings)
pluginManager.initialize_implementations(
additional_inject_factories=[
octoprint_plugin_inject_factory,
settings_plugin_inject_factory
]
)
slicingManager.initialize()
# configure additional template folders for jinja2
template_plugins = pluginManager.get_implementations(octoprint.plugin.TemplatePlugin)
additional_template_folders = []
for plugin in template_plugins.values():
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")
# configure timelapse
octoprint.timelapse.configureTimelapse()
# setup command triggers
events.CommandTrigger(printer)
if self._debug:
events.DebugEventListener()
if settings().getBoolean(["accessControl", "enabled"]):
userManagerName = settings().get(["accessControl", "userManager"])
try:
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)
app.wsgi_app = util.ReverseProxied(
app.wsgi_app,
settings().get(["server", "reverseProxy", "prefixHeader"]),
settings().get(["server", "reverseProxy", "schemeHeader"]),
settings().get(["server", "reverseProxy", "hostHeader"]),
settings().get(["server", "reverseProxy", "prefixFallback"]),
settings().get(["server", "reverseProxy", "schemeFallback"]),
settings().get(["server", "reverseProxy", "hostFallback"])
)
secret_key = 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))
settings().set(["server", "secretKey"], secret_key)
settings().save()
app.secret_key = secret_key
loginManager = LoginManager()
loginManager.session_protection = "strong"
loginManager.user_callback = load_user
if userManager is None:
loginManager.anonymous_user = users.DummyUser
principals.identity_loaders.appendleft(users.dummy_identity_loader)
loginManager.init_app(app)
if self._host is None:
self._host = settings().get(["server", "host"])
if self._port is None:
self._port = settings().getInt(["server", "port"])
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 name, plugin in blueprint_plugins.items():
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._router = SockJSRouter(self._createSocketConnection, "/sockjs")
upload_suffixes = dict(name=settings().get(["server", "uploads", "nameSuffix"]), path=settings().get(["server", "uploads", "pathSuffix"]))
self._tornado_app = Application(self._router.urls + [
(r"/downloads/timelapse/([^/]*\.mpg)", util.tornado.LargeResponseHandler, dict(path=settings().getBaseFolder("timelapse"), as_attachment=True)),
(r"/downloads/files/local/([^/]*\.(gco|gcode|g|stl))", util.tornado.LargeResponseHandler, dict(path=settings().getBaseFolder("uploads"), as_attachment=True)),
(r"/downloads/logs/([^/]*)", util.tornado.LargeResponseHandler, dict(path=settings().getBaseFolder("logs"), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.admin_validator))),
(r"/downloads/camera/current", util.tornado.UrlForwardHandler, dict(url=settings().get(["webcam", "snapshot"]), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.user_validator))),
(r".*", util.tornado.UploadStorageFallbackHandler, dict(fallback=util.tornado.WsgiInputContainer(app.wsgi_app), file_prefix="octoprint-file-upload-", file_suffix=".tmp", suffixes=upload_suffixes))
])
max_body_sizes = [
("POST", r"/api/files/([^/]*)", settings().getInt(["server", "uploads", "maxSize"]))
]
self._server = util.tornado.CustomHTTPServer(self._tornado_app, max_body_sizes=max_body_sizes, default_max_body_size=settings().getInt(["server", "maxSize"]))
self._server.listen(self._port, address=self._host)
eventManager.fire(events.Events.STARTUP)
if settings().getBoolean(["serial", "autoconnect"]):
(port, baudrate) = settings().get(["serial", "port"]), settings().getInt(["serial", "baudrate"])
printer_profile = printerProfileManager.get_default()
connectionOptions = getConnectionOptions()
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
observer = Observer()
observer.schedule(util.watchdog.GcodeWatchdogHandler(fileManager, printer), settings().getBaseFolder("watched"))
observer.start()
ioloop = IOLoop.instance()
# run our startup plugins
octoprint.plugin.call_plugin(octoprint.plugin.StartupPlugin,
"on_startup",
args=(self._host, self._port))
# prepare our after startup function
def on_after_startup():
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
# or service xmls or the like). While they are working though the ioloop would block. Therefore we'll
# create a single use thread in which to perform our after-startup-tasks, start that and hand back
# control to the ioloop
def work():
octoprint.plugin.call_plugin(octoprint.plugin.StartupPlugin,
"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!")
observer.stop()
observer.join()
octoprint.plugin.call_plugin(octoprint.plugin.ShutdownPlugin,
"on_shutdown")
atexit.register(on_shutdown)
try:
ioloop.start()
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:")
def _createSocketConnection(self, session):
global printer, fileManager, analysisQueue, userManager, eventManager
return util.sockjs.PrinterStateConnection(printer, fileManager, analysisQueue, userManager, eventManager, pluginManager, session)
def _checkForRoot(self):
if "geteuid" in dir(os) and os.geteuid() == 0:
exit("You should not run OctoPrint as root!")
def _initSettings(self, configfile, basedir):
settings(init=True, basedir=basedir, configfile=configfile)
def _initLogging(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": "ERROR"
}
},
"root": {
"level": "INFO",
"handlers": ["console", "file"]
}
}
if debug:
defaultConfig["root"]["level"] = "DEBUG"
if logConf is None:
logConf = os.path.join(settings().settings_dir, "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")
if __name__ == "__main__":
server = Server()
server.run()