diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 115ed3f1..e5f45000 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -677,6 +677,11 @@ class Server(): assets = CustomDirectoryEnvironment(app) assets.debug = not 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"]) diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index 41d84e62..ebc3290a 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -11,6 +11,8 @@ import flask import flask.ext.login import flask.ext.principal import flask.ext.assets +import webassets.updater +import webassets.utils import functools import time import uuid @@ -459,6 +461,46 @@ class PluginAssetResolver(flask.ext.assets.FlaskResolver): import os return os.path.normpath(os.path.join(ctx.environment.directory, target)) +##~~ Webassets updater that takes changes in the configuration into account + +class SettingsCheckUpdater(webassets.updater.BaseUpdater): + + updater = "always" + + def __init__(self): + self._delegate = webassets.updater.get_updater(self.__class__.updater) + + def needs_rebuild(self, bundle, ctx): + return self._delegate.needs_rebuild(bundle, ctx) or self.changed_settings(ctx) + + def changed_settings(self, ctx): + import json + + if not ctx.cache: + return False + + cache_key = ('octo', 'settings') + current_hash = webassets.utils.hash_func(json.dumps(settings().effective_yaml)) + cached_hash = ctx.cache.get(cache_key) + # This may seem counter-intuitive, but if no cache entry is found + # then we actually return "no update needed". This is because + # otherwise if no cache / a dummy cache is used, then we would be + # rebuilding every single time. + if not cached_hash is None: + return cached_hash != current_hash + return False + + def build_done(self, bundle, ctx): + import json + + self._delegate.build_done(bundle, ctx) + if not ctx.cache: + return + + cache_key = ('octo', 'settings') + cache_value = webassets.utils.hash_func(json.dumps(settings().effective_yaml)) + ctx.cache.set(cache_key, cache_value) + ##~~ plugin assets collector def collect_plugin_assets(enable_gcodeviewer=True, enable_timelapse=True, preferred_stylesheet="css"): diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index 80a78cad..74db8425 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -250,7 +250,7 @@ default_settings = { "enabled": True }, "webassets": { - "minify": True, + "minify": False, "bundle": True }, "virtualPrinter": { @@ -415,7 +415,7 @@ class Settings(object): if source is None: raise TemplateNotFound(template) mtime = self._settings._mtime - return source, None, lambda: mtime == self._settings._last_modified + return source, None, lambda: mtime == self._settings.last_modified def list_templates(self): scripts = self._settings.get(["scripts"], merged=True) @@ -523,13 +523,23 @@ class Settings(object): return map(process_control, controls) + @property + def effective(self): + import octoprint.util + return octoprint.util.dict_merge(default_settings, self._config) + + @property + def effective_yaml(self): + import yaml + return yaml.safe_dump(self.effective) + #~~ load and save def load(self, migrate=False): if os.path.exists(self._configfile) and os.path.isfile(self._configfile): with open(self._configfile, "r") as f: self._config = yaml.safe_load(f) - self._mtime = self._last_modified + self._mtime = self.last_modified # changed from else to handle cases where the file exists, but is empty / 0 bytes if not self._config: self._config = {} @@ -768,7 +778,7 @@ class Settings(object): return True @property - def _last_modified(self): + def last_modified(self): """ Returns: int: The last modification time of the configuration file. @@ -924,7 +934,7 @@ class Settings(object): if len(path) == 0: return - if self._mtime is not None and self._last_modified != self._mtime: + if self._mtime is not None and self.last_modified != self._mtime: self.load() config = self._config