diff --git a/docs/jsclientlib/socket.rst b/docs/jsclientlib/socket.rst index 21c32d4c..835ba839 100644 --- a/docs/jsclientlib/socket.rst +++ b/docs/jsclientlib/socket.rst @@ -102,7 +102,7 @@ Instructs the server to decrease the message rate by 500ms. -.. sec-jsclient-socket-throttling: +.. _sec-jsclient-socket-throttling: Communication Throttling ======================== diff --git a/docs/jsclientlib/timelapse.rst b/docs/jsclientlib/timelapse.rst index b2879553..5a2e9047 100644 --- a/docs/jsclientlib/timelapse.rst +++ b/docs/jsclientlib/timelapse.rst @@ -3,26 +3,87 @@ :mod:`OctoPrint.timelapse` -------------------------- -.. todo:: +.. js:function:: OctoPrint.timelapse.get(unrendered, opts) - Needs to be documented + Get a list of all timelapses and the current timelapse config. -.. js:function:: OctoPrint.timelapse.get(opts) + If ``unrendered`` is true, also retrieve the list of unrendered + timelapses. + + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response .. js:function:: OctoPrint.timelapse.list(opts) + Get the lists of rendered and unrendered timelapses. The returned promis + will be resolved with an object containing the properties ``rendered`` + which will have the list of rendered timelapses, and ``unrendered`` which + will have the list of unrendered timelapses. + + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + +.. js:function:: OctoPrint.timelapse.listRendered(opts) + + Get the list of rendered timelapses. + + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + +.. js:function:: OctoPrint.timelapse.listUnrendered(opts) + + Get the list of unrendered timelapses. + + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.timelapse.download(filename, opts) + Download the rendered timelapse ``filename``. + + :param string filename: The name of the rendered timelapse to download + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.timelapse.delete(filename, opts) + Delete the rendered timelapse ``filename``. + + :param string filename: The name of the rendered timelapse to delete + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.timelapse.deleteUnrendered(name, opts) + Delete the unrendered timelapse ``name``. + + :param string name: The name of the unrendered timelapse to delete + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.timelapse.renderUnrendered(name, opts) + Render the unrendered timelapse ``name``. + + :param string name: The name of the unrendered timelapse to render + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.timelapse.getConfig(opts) + Get the current timelapse configuration. + + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.timelapse.saveConfig(config, opts) + Save the timelapse configuration. + + :param object config: The config to save + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. seealso:: :ref:`Timelapse API ` diff --git a/docs/jsclientlib/users.rst b/docs/jsclientlib/users.rst index 70487563..c86b5d19 100644 --- a/docs/jsclientlib/users.rst +++ b/docs/jsclientlib/users.rst @@ -3,31 +3,104 @@ :mod:`OctoPrint.users` ---------------------- -.. todo:: +.. note:: - Needs to be documented + Most methods here require that the used API token or a the existing browser session + has admin rights *or* corresponds to the user to be modified. Some methods + definitely require admin rights. .. js:function:: OctoPrint.users.list(opts) + Get a list of all registered users. + + Requires admin rights. + + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.users.get(name, opts) + Get information about a specific user. + + :param string name: The user's name + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.users.add(user, opts) + Add a new user. + + Requires admin rights. + + :param object user: The new user + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.users.update(name, active, admin, opts) + Update an existing user. + + Requires admin rights. + + :param string name: The user's name + :param bool active: The new ``active`` state of the user + :param bool admin: The new ``admin`` state of the user + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.users.delete(name, opts) + Delete an existing user. + + Requires admin rights. + + :param string name: The user's name + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.users.changePassword(name, password, opts) + Change the password for a user. + + :param string name: The user's name + :param string password: The new password + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.users.generateApiKey(name, opts) + Generate a new API key for a user. + + :param string name: The user's name + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.users.resetApiKey(name, opts) + Reset the API key for a user to being unset. + + :param string name: The user's name + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.users.getSettings(name, opts) + Get the settings for a user. + + :param string name: The user's name + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.users.saveSettings(name, settings, opts) + Save the settings for a user. + + :param string name: The user's name + :param object settings: The new settings, may be a partial set of settings which will be merged unto the current ones + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. seealso:: - :ref:`User API ` + :ref:`User API ` The documentation of the underlying user API. diff --git a/docs/jsclientlib/wizard.rst b/docs/jsclientlib/wizard.rst index d6398c9f..3a124f94 100644 --- a/docs/jsclientlib/wizard.rst +++ b/docs/jsclientlib/wizard.rst @@ -3,10 +3,27 @@ :mod:`OctoPrint.wizard` ----------------------- -.. todo:: +.. note:: - Needs to be documented + All methods here require that the used API token or a the existing browser session + has admin rights. .. js:function:: OctoPrint.wizard.get(opts) + Retrieve additional data about registered wizards. + + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + .. js:function:: OctoPrint.wizard.finish(handled, opts) + + Inform wizards that the wizard dialog has been finished. + + :param list handled: List of identifiers of handled wizards + :param object opts: Additional options for the request + :returns Promise: A `jQuery Promise `_ for the request's response + +.. seealso:: + + :ref:`Wizard API ` + The documentation of the underlying wizard API. diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index e51622a5..4c0f329f 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -473,7 +473,9 @@ class LessSimpleCache(BaseCache): def __init__(self, threshold=500, default_timeout=300): BaseCache.__init__(self, default_timeout=default_timeout) + self._mutex = threading.RLock() self._cache = {} + self._bypassed = set() self.clear = self._cache.clear self._threshold = threshold @@ -482,27 +484,35 @@ class LessSimpleCache(BaseCache): now = time.time() for idx, (key, (expires, _)) in enumerate(self._cache.items()): if expires is not None and expires <= now or idx % 3 == 0: - self._cache.pop(key, None) + with self._mutex: + self._cache.pop(key, None) def get(self, key): import pickle now = time.time() - expires, value = self._cache.get(key, (0, None)) + with self._mutex: + expires, value = self._cache.get(key, (0, None)) if expires is None or expires > now: return pickle.loads(value) def set(self, key, value, timeout=None): import pickle - self._prune() - self._cache[key] = (self.calculate_timeout(timeout=timeout), - pickle.dumps(value, pickle.HIGHEST_PROTOCOL)) + + with self._mutex: + self._prune() + self._cache[key] = (self.calculate_timeout(timeout=timeout), + pickle.dumps(value, pickle.HIGHEST_PROTOCOL)) + if key in self._bypassed: + self._bypassed.remove(key) def add(self, key, value, timeout=None): - self.set(key, value, timeout=None) - self._cache.setdefault(key, self._cache[key]) + with self._mutex: + self.set(key, value, timeout=None) + self._cache.setdefault(key, self._cache[key]) def delete(self, key): - self._cache.pop(key, None) + with self._mutex: + self._cache.pop(key, None) def calculate_timeout(self, timeout=None): if timeout is None: @@ -514,7 +524,8 @@ class LessSimpleCache(BaseCache): def over_threshold(self): if self._threshold is None: return False - return len(self._cache) > self._threshold + with self._mutex: + return len(self._cache) > self._threshold def __getitem__(self, key): return self.get(key) @@ -526,7 +537,16 @@ class LessSimpleCache(BaseCache): return self.delete(key) def __contains__(self, key): - return key in self._cache + with self._mutex: + return key in self._cache + + def set_bypassed(self, key): + with self._mutex: + self._bypassed.add(key) + + def is_bypassed(self, key): + with self._mutex: + return key in self._bypassed _cache = LessSimpleCache() @@ -552,11 +572,13 @@ def cached(timeout=5 * 60, key=lambda: "view:%s" % flask.request.path, unless=No # bypass the cache if "unless" condition is true if callable(unless) and unless(): logger.debug("Cache for {path} bypassed, calling wrapped function".format(path=flask.request.path)) + _cache.set_bypassed(cache_key) return f_with_duration(*args, **kwargs) # also bypass the cache if it's disabled completely if not settings().getBoolean(["devel", "cache", "enabled"]): logger.debug("Cache for {path} disabled, calling wrapped function".format(path=flask.request.path)) + _cache.set_bypassed(cache_key) return f_with_duration(*args, **kwargs) rv = _cache.get(cache_key) @@ -575,6 +597,7 @@ def cached(timeout=5 * 60, key=lambda: "view:%s" % flask.request.path, unless=No # do not store if the "unless_response" condition is true if callable(unless_response) and unless_response(rv): logger.debug("Not caching result for {path} (key: {key}), bypassed".format(path=flask.request.path, key=cache_key)) + _cache.set_bypassed(cache_key) return rv # store it in the cache @@ -591,6 +614,11 @@ def is_in_cache(key=lambda: "view:%s" % flask.request.path): key = key() return key in _cache +def is_cache_bypassed(key=lambda: "view:%s" % flask.request.path): + if callable(key): + key = key() + return _cache.is_bypassed(key) + def cache_check_headers(): return "no-cache" in flask.request.cache_control or "no-cache" in flask.request.pragma diff --git a/src/octoprint/server/views.py b/src/octoprint/server/views.py index 7153ea55..9b35d47b 100644 --- a/src/octoprint/server/views.py +++ b/src/octoprint/server/views.py @@ -146,6 +146,9 @@ def in_cache(): elif util.flask.is_in_cache(key): _logger.info("Found path {} in cache (key: {}), signaling as cached".format(path, key)) return response + elif util.flask.is_cache_bypassed(key): + _logger.info("Path {} was bypassed from cache (key: {}), signaling as cached".format(path, key)) + return response else: _logger.debug("Path {} not yet cached (key: {}), signaling as missing".format(path, key)) return abort(404) diff --git a/src/octoprint/static/js/app/client/timelapse.js b/src/octoprint/static/js/app/client/timelapse.js index 4bfde8a6..fbbdf1e3 100644 --- a/src/octoprint/static/js/app/client/timelapse.js +++ b/src/octoprint/static/js/app/client/timelapse.js @@ -7,25 +7,52 @@ })(window || this, function(OctoPrint, $) { var url = "api/timelapse"; + var downloadUrl = "downloads/timelapse"; + var timelapseUrl = function(filename) { return url + "/" + filename; }; + var timelapseDownloadUrl = function(filename) { + return downloadUrl + "/" + filename; + }; + var unrenderedTimelapseUrl = function(name) { return url + "/unrendered/" + name; }; - var getTimelapseData = function (opts) { + var getTimelapseData = function (unrendered, opts) { + if (unrendered) { + opts = opts || {}; + opts.data = {unrendered: unrendered}; + } return OctoPrint.get(url, opts); }; OctoPrint.timelapse = { get: getTimelapseData, - list: function (opts) { + list: function(opts) { var deferred = $.Deferred(); - getTimelapseData(opts) + getTimelapseData(true, opts) + .done(function (response, status, request) { + deferred.resolve({ + rendered: response.files, + unrendered: response.unrendered + }, status, request); + }) + .fail(function () { + deferred.reject.apply(null, arguments); + }); + + return deferred.promise(); + }, + + listRendered: function (opts) { + var deferred = $.Deferred(); + + getTimelapseData(false, opts) .done(function (response, status, request) { deferred.resolve(response.files, status, request); }) @@ -36,8 +63,22 @@ return deferred.promise(); }, + listUnrendered: function (opts) { + var deferred = $.Deferred(); + + getTimelapseData(true, opts) + .done(function (response, status, request) { + deferred.resolve(response.unrendered, status, request); + }) + .fail(function () { + deferred.reject.apply(null, arguments); + }); + + return deferred.promise(); + }, + download: function (filename, opts) { - return OctoPrint.download(timelapseUrl(filename), opts); + return OctoPrint.download(timelapseDownloadUrl(filename), opts); }, delete: function (filename, opts) { @@ -54,7 +95,7 @@ getConfig: function (opts) { var deferred = $.Deferred(); - getTimelapseData(opts) + getTimelapseData(false, opts) .done(function (response, status, request) { deferred.resolve(response.config, status, request); }) diff --git a/src/octoprint/static/js/app/viewmodels/timelapse.js b/src/octoprint/static/js/app/viewmodels/timelapse.js index f44ad165..fb1c3604 100644 --- a/src/octoprint/static/js/app/viewmodels/timelapse.js +++ b/src/octoprint/static/js/app/viewmodels/timelapse.js @@ -125,7 +125,7 @@ $(function() { ); self.requestData = function() { - OctoPrint.timelapse.get({ data: { unrendered: true} }) + OctoPrint.timelapse.get(true) .done(self.fromResponse); };