From c1880a7006083e73c05b8f1499de7af941b11404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 7 Sep 2015 13:57:10 +0200 Subject: [PATCH] New UiPlugin for serving custom UIs on / depending on request --- src/octoprint/plugin/__init__.py | 3 +- src/octoprint/plugin/types.py | 9 ++ src/octoprint/server/util/flask.py | 4 +- src/octoprint/server/views.py | 127 +++++++++++++++++++---------- 4 files changed, 95 insertions(+), 48 deletions(-) diff --git a/src/octoprint/plugin/__init__.py b/src/octoprint/plugin/__init__.py index 1df93a9b..686c7836 100644 --- a/src/octoprint/plugin/__init__.py +++ b/src/octoprint/plugin/__init__.py @@ -104,7 +104,8 @@ def plugin_manager(init=False, plugin_folders=None, plugin_types=None, plugin_en SlicerPlugin, AppPlugin, ProgressPlugin, - WizardPlugin] + WizardPlugin, + UiPlugin] if plugin_entry_points is None: plugin_entry_points = "octoprint.plugin" if plugin_disabled_list is None: diff --git a/src/octoprint/plugin/types.py b/src/octoprint/plugin/types.py index 9fdbe024..9af9c617 100644 --- a/src/octoprint/plugin/types.py +++ b/src/octoprint/plugin/types.py @@ -485,6 +485,15 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin): return os.path.join(self._basefolder, "templates") +class UiPlugin(OctoPrintPlugin, SortablePlugin): + + def will_handle_ui(self, request): + return False + + def on_ui_render(self, now, request, render_kwargs): + return None + + class WizardPlugin(OctoPrintPlugin, ReloadNeedingPlugin): """ The ``WizardPlugin`` mixin allows plugins to report to OctoPrint whether diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index e14ab1ba..5c6c1199 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -281,11 +281,11 @@ def cached(timeout=5 * 60, key=lambda: "view/%s" % flask.request.path, unless=No if not callable(refreshif) or not refreshif(): rv = _cache.get(cache_key) if rv is not None: - logger.debug("Serving entry for {path} from cache".format(path=flask.request.path)) + logger.debug("Serving entry for {path} from cache (key: {key})".format(path=flask.request.path, key=cache_key)) return rv # get value from wrapped function - logger.debug("No cache entry or refreshing cache for {path}, calling wrapped function".format(path=flask.request.path)) + logger.debug("No cache entry or refreshing cache for {path} (key: {key}), calling wrapped function".format(path=flask.request.path, key=cache_key)) rv = f(*args, **kwargs) # do not store if the "unless_response" condition is true diff --git a/src/octoprint/server/views.py b/src/octoprint/server/views.py index be79b626..942c3bfb 100644 --- a/src/octoprint/server/views.py +++ b/src/octoprint/server/views.py @@ -22,21 +22,93 @@ from . import util import logging _logger = logging.getLogger(__name__) -@app.route("/") -@util.flask.cached(refreshif=lambda: util.flask.cache_check_headers() or "_refresh" in request.values, - key=lambda: "view/%s/%s" % (request.path, g.locale), - unless_response=util.flask.cache_check_response_headers) -def index(): +_templates = None +_plugin_names = None +_plugin_vars = None +@app.route("/") +def index(): + force_refresh = util.flask.cache_check_headers() or "_refresh" in request.values + + global _templates, _plugin_names, _plugin_vars + + if force_refresh or _templates is None or _plugin_names is None or _plugin_vars is None: + _templates, _plugin_names, _plugin_vars = _process_templates() + + now = datetime.datetime.utcnow() + render_kwargs = _get_render_kwargs(_templates, _plugin_names, _plugin_vars, now) + + def get_cached_view(key, view): + return util.flask.cached(refreshif=lambda: force_refresh, + key=lambda: "ui:{}:{}".format(key, g.locale), + unless_response=util.flask.cache_check_response_headers)(view) + + ui_plugins = pluginManager.get_implementations(octoprint.plugin.UiPlugin, sorting_context="UiPlugin.on_ui_render") + for plugin in ui_plugins: + if plugin.will_handle_ui(request): + # plugin claims responsibility, let it render the UI + cached = get_cached_view(plugin._identifier, plugin.on_ui_render) + response = cached(now, request, render_kwargs) + if response is not None: + break + + else: + # no plugin took an interest, we'll use the default UI + def make_default_ui(): + r = make_response(render_template("index.jinja2", **render_kwargs)) + if bool(render_kwargs["templates"]["wizard"]["order"]): + r = util.flask.add_non_caching_response_headers(response) + return r + + cached = get_cached_view("_default", make_default_ui) + response = cached() + + response.headers["Last-Modified"] = now + + return response + + +def _get_render_kwargs(templates, plugin_names, plugin_vars, now): #~~ a bunch of settings + enable_accesscontrol = userManager is not None + first_run = settings().getBoolean(["server", "firstRun"]) + locales = dict((l.language, dict(language=l.language, display=l.display_name, english=l.english_name)) for l in LOCALES) + + #~~ prepare full set of template vars for rendering + + wizard = bool(templates["wizard"]["order"]) + render_kwargs = dict( + webcamStream=settings().get(["webcam", "stream"]), + enableTemperatureGraph=settings().get(["feature", "temperatureGraph"]), + enableAccessControl=enable_accesscontrol, + enableSdSupport=settings().get(["feature", "sdSupport"]), + firstRun=first_run, + debug=debug, + version=VERSION, + display_version=DISPLAY_VERSION, + branch=BRANCH, + gcodeMobileThreshold=settings().get(["gcodeViewer", "mobileSizeThreshold"]), + gcodeThreshold=settings().get(["gcodeViewer", "sizeThreshold"]), + uiApiKey=UI_API_KEY, + templates=templates, + pluginNames=plugin_names, + locales=locales, + wizard=wizard, + now=now + ) + render_kwargs.update(plugin_vars) + + return render_kwargs + + +def _process_templates(): + enable_accesscontrol = userManager is not None first_run = settings().getBoolean(["server", "firstRun"]) 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"]) - locales = dict((l.language, dict(language=l.language, display=l.display_name, english=l.english_name)) for l in LOCALES) ##~~ prepare templates @@ -327,43 +399,7 @@ def index(): templates[t]["entries"].update(template_sorting[t]["custom_insert_entries"](sorted_missing)) templates[t]["order"] = template_sorting[t]["custom_insert_order"](templates[t]["order"], sorted_missing) - #~~ prepare full set of template vars for rendering - - wizard = bool(templates["wizard"]["order"]) - now = datetime.datetime.utcnow() - render_kwargs = dict( - webcamStream=settings().get(["webcam", "stream"]), - enableTemperatureGraph=settings().get(["feature", "temperatureGraph"]), - enableAccessControl=enable_accesscontrol, - enableSdSupport=settings().get(["feature", "sdSupport"]), - firstRun=first_run, - debug=debug, - version=VERSION, - display_version=DISPLAY_VERSION, - branch=BRANCH, - gcodeMobileThreshold=settings().get(["gcodeViewer", "mobileSizeThreshold"]), - gcodeThreshold=settings().get(["gcodeViewer", "sizeThreshold"]), - uiApiKey=UI_API_KEY, - templates=templates, - pluginNames=plugin_names, - locales=locales, - wizard=wizard, - now=now - ) - render_kwargs.update(plugin_vars) - - #~~ render! - - response = make_response(render_template( - "index.jinja2", - **render_kwargs - )) - response.headers["Last-Modified"] = now - - if wizard: - response = util.flask.add_non_caching_response_headers(response) - - return response + return templates, plugin_names, plugin_vars def _process_template_configs(name, implementation, configs, rules): @@ -454,7 +490,8 @@ def robotsTxt(): @app.route("/i18n//.js") -@util.flask.cached(refreshif=lambda: util.flask.cache_check_headers() or "_refresh" in request.values, key=lambda: "view/%s/%s" % (request.path, g.locale)) +@util.flask.cached(refreshif=lambda: util.flask.cache_check_headers() or "_refresh" in request.values, + key=lambda: "{}:{}".format(request.path, g.locale)) def localeJs(locale, domain): messages = dict() plural_expr = None