diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index 11ef9bc3..e12a1188 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -371,7 +371,9 @@ def cached(timeout=5 * 60, key=lambda: "view:%s" % flask.request.path, unless=No return decorator def is_in_cache(key=lambda: "view:%s" % flask.request.path): - return key() in _cache + if callable(key): + key = key() + return key in _cache def cache_check_headers(): return "no-cache" in flask.request.cache_control or "no-cache" in flask.request.pragma @@ -400,10 +402,11 @@ class PreemptiveCache(object): self.cachefile = cachefile self._lock = threading.RLock() + self._log_lock = threading.RLock() self._logger = logging.getLogger(__name__ + "." + self.__class__.__name__) self._log_access = True - def record(self, data, unless=None): + def record(self, data, unless=None, root=None): if callable(unless) and unless(): return @@ -412,12 +415,32 @@ class PreemptiveCache(object): entry_data = entry_data() if entry_data is not None: + if root is None: + from flask import request + root = request.path + self.add_data(root, entry_data) + + def has_record(self, data, root=None): + if callable(data): + data = data() + + if data is None: + return False + + if root is None: from flask import request - self.add_data(request.path, entry_data) + root = request.path + + all_data = self.get_data(root) + for existing in all_data: + if self._compare_data(data, existing): + return True + + return False @contextlib.contextmanager def disable_access_logging(self): - with self._lock: + with self._log_lock: self._log_access = False yield self._log_access = True @@ -482,20 +505,19 @@ class PreemptiveCache(object): self.set_all_data(all_data) def add_data(self, root, data): - from octoprint.util import dict_filter - - def strip_ignored(d): - return dict_filter(d, lambda k, v: not k.startswith("_")) - - def compare(a, b): - return set(strip_ignored(a).items()) == set(strip_ignored(b).items()) + with self._log_lock: + if not self._log_access: + self._logger.debug( + "Not updating timestamp and counter for {} and {!r}, currently flagged as disabled".format(root, + data)) + return def split_matched_and_unmatched(entry, entries): matched = [] unmatched = [] for e in entries: - if compare(e, entry): + if self._compare_data(e, entry): matched.append(e) else: unmatched.append(e) @@ -503,12 +525,6 @@ class PreemptiveCache(object): return matched, unmatched with self._lock: - if not self._log_access: - self._logger.debug( - "Not updating timestamp and counter for {} and {!r}, currently flagged as disabled".format(root, - data)) - return - cache_data = self.get_all_data() if not root in cache_data: @@ -537,6 +553,14 @@ class PreemptiveCache(object): self.set_data(root, [to_persist] + other) + def _compare_data(self, a, b): + from octoprint.util import dict_filter + + def strip_ignored(d): + return dict_filter(d, lambda k, v: not k.startswith("_")) + + return set(strip_ignored(a).items()) == set(strip_ignored(b).items()) + def preemptively_cached(cache, data, unless=None): def decorator(f): diff --git a/src/octoprint/server/views.py b/src/octoprint/server/views.py index bcc29ce1..b26a7b5c 100644 --- a/src/octoprint/server/views.py +++ b/src/octoprint/server/views.py @@ -30,25 +30,63 @@ _logger = logging.getLogger(__name__) _valid_id_re = re.compile("[a-z_]+") _valid_div_re = re.compile("[a-zA-Z_-]+") +def _preemptive_unless(root=None, path=None): + if root is None: + root = request.url_root + if path is None: + path = request.path + + return not settings().getBoolean(["devel", "cache", "preemptive"]) \ + or path in settings().get(["server", "preemptiveCache", "exceptions"]) \ + or not (root.startswith("http://") or root.startswith("https://")) + +def _preemptive_data(path=None, base_url=None): + if path is None: + path = request.path + if base_url is None: + base_url = request.url_root + + return dict(path=path, + base_url=base_url, + query_string="l10n={}".format(g.locale.language) if g.locale else "en") + +def _cache_key(url=None, locale=None): + if url is None: + url = request.base_url + if locale is None: + locale = g.locale.language if g.locale else "en" + + return "view:{}:{}".format(url, locale) + @app.route("/cached.gif") def in_cache(): url = request.base_url.replace("/cached.gif", "/") - key = lambda: "view:{}:{}".format(url, g.locale.language if g.locale else "en") - if not util.flask.is_in_cache(key): - return abort(404) + path = request.path.replace("/cached.gif", "/") + + key = _cache_key(url) + data = _preemptive_data(path=path) response = make_response(bytes(base64.b64decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"))) response.headers["Content-Type"] = "image/gif" - return response + + if _preemptive_unless(root=url, path=path) or not preemptiveCache.has_record(data, root=path): + _logger.info("Preemptive cache not active for path {} and data {!r}, signaling as cached".format(path, data)) + return response + elif util.flask.is_in_cache(key): + _logger.info("Found path {} in cache (key: {}), signaling as cached".format(path, key)) + return response + else: + _logger.info("Path {} not yet cached (key: {}), signaling as missing".format(path, key)) + return abort(404) @app.route("/") @util.flask.preemptively_cached(cache=preemptiveCache, - data=lambda: dict(path=request.path, base_url=request.url_root, query_string="l10n={}".format(g.locale.language) if g.locale else "en"), - unless=lambda: request.url_root in settings().get(["server", "preemptiveCache", "exceptions"]) or not (request.url_root.startswith("http://") or request.url_root.startswith("https://"))) + data=_preemptive_data, + unless=_preemptive_unless) @util.flask.conditional(lambda: _check_etag_and_lastmodified_for_index(), NOT_MODIFIED) @util.flask.cached(timeout=-1, refreshif=lambda cached: _validate_cache_for_index(cached), - key=lambda: "view:{}:{}".format(request.base_url, g.locale.language if g.locale else "en"), + key=_cache_key, unless_response=lambda response: util.flask.cache_check_response_headers(response)) @util.flask.etagged(lambda _: _compute_etag_for_index()) @util.flask.lastmodified(lambda _: _compute_date_for_index()) diff --git a/src/octoprint/static/intermediary.html b/src/octoprint/static/intermediary.html index 56c621e5..173c7128 100644 --- a/src/octoprint/static/intermediary.html +++ b/src/octoprint/static/intermediary.html @@ -162,9 +162,15 @@ var message = window.document.getElementById("message"); + var indexCached = false; var indexCachedCallback = function(result) { if (result == "load") { + if (indexCached) { + return; + } + // our cached.gif loaded, so the index is cached now, let's reload + indexCached = true; message.className = "pulsate1 green"; message.innerText = "OctoPrint server ready, reloading page...";