From 1f0dbf8f7f0cce5376b22d9e410516328bb8474f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 23 Jul 2015 12:19:27 +0200 Subject: [PATCH 01/58] maintenance branch is now 1.2.5-dev --- .versioneer-lookup | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.versioneer-lookup b/.versioneer-lookup index 7c9750a02..db6d9a7b7 100644 --- a/.versioneer-lookup +++ b/.versioneer-lookup @@ -10,10 +10,10 @@ # master shall not use the lookup table, only tags master -# maintenance is currently the branch for preparation of maintenance release 1.2.4 +# maintenance is currently the branch for preparation of maintenance release 1.2.5 # so are any fix/... branches -maintenance 1.2.4-dev 3761995aff94acf32495556730f133a1626245b3 -fix/.* 1.2.4-dev 3761995aff94acf32495556730f133a1626245b3 +maintenance 1.2.5-dev 9a6099ffc2982455d631c9d68a3273d9eb55885c +fix/.* 1.2.5-dev 9a6099ffc2982455d631c9d68a3273d9eb55885c # every other branch is a development branch and thus gets resolved to 1.3.0-dev for now .* 1.3.0-dev 198d3450d94be1a2 From 8bbb642a565cdaad90ecff844a605bbe893a7e08 Mon Sep 17 00:00:00 2001 From: Thomas Hou Date: Tue, 28 Jul 2015 11:51:32 +0200 Subject: [PATCH 02/58] fixed bug: when new job start, job progress is last 100%. (cherry picked from commit 200ad3b) --- src/octoprint/printer/standard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/octoprint/printer/standard.py b/src/octoprint/printer/standard.py index a652e6e7d..c332182c5 100644 --- a/src/octoprint/printer/standard.py +++ b/src/octoprint/printer/standard.py @@ -389,6 +389,7 @@ def start_print(self): self._timeEstimationData = TimeEstimationHelper(rolling_window=rolling_window, threshold=threshold, countdown=countdown) self._lastProgressReport = None + self._setProgressData(0, None, None, None) self._setCurrentZ(None) self._comm.startPrint() From 5808c1bd8abc7d9d9f30bfb69ad7062e726e6f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 29 Jul 2015 09:16:55 +0200 Subject: [PATCH 03/58] Added @masterhou to AUTHORS.md (cherry picked from commit 7c90fe5) --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 07f0fc9ab..0309fdf53 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -52,6 +52,7 @@ date of first contribution): * [Lucas Clemente](https://github.com/lucas-clemente) * [Andrew Erickson](https://github.com/aerickson) * [Nicanor Romero Venier](https://github.com/nicanor-romero) + * [Thomas Hou](https://github.com/masterhou) OctoPrint started off as a fork of [Cura](https://github.com/daid/Cura) by [Daid Braam](https://github.com/daid). Parts of its communication layer and From 8a28220e638a4330990506d5e827a873ce48ea44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 27 Jul 2015 23:12:55 +0200 Subject: [PATCH 04/58] responsive => snappy Finally found a replacement for the word "responsive", which these days is almost inclusively understood as "viewport adjusting" instead of its original meaning "reacting quickly". Thanks to the javascript world, even "reactive" isn't an option anymore, this term has now been hijacked as well. (cherry picked from commit 2e27b94) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 57ebc9abb..190f1e759 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ OctoPrint ========= -OctoPrint provides a responsive web interface for controlling a 3D printer (RepRap, Ultimaker, ...). It is Free Software +OctoPrint provides a snappy web interface for controlling a 3D printer (RepRap, Ultimaker, ...). It is Free Software and released under the [GNU Affero General Public License V3](http://www.gnu.org/licenses/agpl.html). Its website can be found at [octoprint.org](http://octoprint.org). From 738c699a6e868241ecc6ed4c1b5e652cc38afcaa Mon Sep 17 00:00:00 2001 From: Mark Walker Date: Thu, 30 Jul 2015 10:43:10 +0200 Subject: [PATCH 05/58] Fix for #1001 connection tab not unfolding Defer collapsing the connection element until the whole wrapper part is visible (it's hidden for non-admins). (cherry picked from commit af67d8b) --- .../static/js/app/viewmodels/connection.js | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/octoprint/static/js/app/viewmodels/connection.js b/src/octoprint/static/js/app/viewmodels/connection.js index 9c35936e4..7f55d6075 100644 --- a/src/octoprint/static/js/app/viewmodels/connection.js +++ b/src/octoprint/static/js/app/viewmodels/connection.js @@ -86,6 +86,15 @@ $(function() { self._processStateData(data.state); }; + self.openOrCloseOnStateChange = function() { + var connectionTab = $("#connection"); + if (self.isOperational() && connectionTab.hasClass("in")) { + connectionTab.collapse("hide"); + } else if (!self.isOperational() && !connectionTab.hasClass("in")) { + connectionTab.collapse("show"); + } + } + self._processStateData = function(data) { self.previousIsOperational = self.isOperational(); @@ -97,15 +106,10 @@ $(function() { self.isReady(data.flags.ready); self.isLoading(data.flags.loading); - var connectionTab = $("#connection"); - if (self.previousIsOperational != self.isOperational()) { - if (self.isOperational() && connectionTab.hasClass("in")) { - // connection just got established, close connection tab for now - connectionTab.collapse("hide"); - } else if (!connectionTab.hasClass("in")) { - // connection just dropped, make sure connection tab is open - connectionTab.collapse("show"); - } + if (self.loginState.isAdmin() && self.previousIsOperational != self.isOperational()) { + // only open or close if the panel is visible (for admins) and + // the state just changed to avoid thwarting manual open/close + self.openOrCloseOnStateChange(); } }; @@ -147,6 +151,16 @@ $(function() { self.onStartup = function() { self.requestData(); + + // when isAdmin becomes true the first time, set the panel open or + // closed based on the connection state + var subscription = self.loginState.isAdmin.subscribe(function(newValue) { + if (newValue) { + // wait until after the isAdmin state has run through all subscriptions + setTimeout(self.openOrCloseOnStateChange, 0); + subscription.dispose(); + } + }); }; } From e32869510028f7be2b91d56400770f26fefa83a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 10 Aug 2015 09:40:48 +0200 Subject: [PATCH 06/58] Do not hiccup on unset Content-Type fields in multipart file uploads Just don't set the content_type parameter in that case. Fixes #985 (cherry picked from commit 4bb5be7) --- src/octoprint/server/util/tornado.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/octoprint/server/util/tornado.py b/src/octoprint/server/util/tornado.py index 61614ae60..ce1bdd002 100644 --- a/src/octoprint/server/util/tornado.py +++ b/src/octoprint/server/util/tornado.py @@ -360,13 +360,24 @@ def _on_request_body_finish(self): for name, part in self._parts.iteritems(): if "filename" in part: # add form fields for filename, path, size and content_type for all files contained in the request - fields = dict((self._suffixes[key], value) for (key, value) in dict(name=part["filename"], path=part["path"], size=str(os.stat(part["path"]).st_size), content_type=part["content_type"]).iteritems()) + if not "path" in part: + continue + + parameters = dict( + name=part["filename"], + path=part["path"], + size=str(os.stat(part["path"]).st_size) + ) + if "content_type" in part: + parameters["content_type"] = part["content_type"] + + fields = dict((self._suffixes[key], value) for (key, value) in parameters.iteritems()) for n, p in fields.iteritems(): key = name + "." + n self._new_body += b"--%s\r\n" % self._multipart_boundary self._new_body += b"Content-Disposition: form-data; name=\"%s\"\r\n" % key self._new_body += b"\r\n" - self._new_body += p + b"\r\n" + self._new_body += b"%s\r\n" % p elif "data" in part: self._new_body += b"--%s\r\n" % self._multipart_boundary value = part["data"] @@ -924,4 +935,4 @@ def path_validation_factory(path_filter, status_code=404): def f(path): if not path_filter(path): raise tornado.web.HTTPError(status_code) - return f \ No newline at end of file + return f From 6df57bc1d10a2c4ad6d99ce0b282384064ef6ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 10 Aug 2015 10:11:40 +0200 Subject: [PATCH 07/58] Evaluate return code of pip while updating Should not report a successful update anymore when the pip call failes for whatever reason. (cherry picked from commit 54b981b) --- src/octoprint/plugins/softwareupdate/updaters/pip.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/octoprint/plugins/softwareupdate/updaters/pip.py b/src/octoprint/plugins/softwareupdate/updaters/pip.py index 5199378d8..ef534b83c 100644 --- a/src/octoprint/plugins/softwareupdate/updaters/pip.py +++ b/src/octoprint/plugins/softwareupdate/updaters/pip.py @@ -10,6 +10,7 @@ import pkg_resources from octoprint.util.pip import PipCaller, UnknownPip +from .. import exceptions logger = logging.getLogger("octoprint.plugins.softwareupdate.updaters.pip") console_logger = logging.getLogger("octoprint.plugins.softwareupdate.updaters.pip.console") @@ -44,7 +45,7 @@ def perform_update(target, check, target_version): pip_caller = _get_pip_caller(command=pip_command) if pip_caller is None: - raise RuntimeError("Can't run pip") + raise exceptions.UpdateError("Can't run pip", None) install_arg = check["pip"].format(target_version=target_version) @@ -54,12 +55,16 @@ def perform_update(target, check, target_version): if "dependency_links" in check and check["dependency_links"] and pip_caller >= _pip_version_dependency_links: pip_args += ["--process-dependency-links"] - pip_caller.execute(*pip_args) + returncode, stdout, stderr = pip_caller.execute(*pip_args) + if returncode != 0: + raise exceptions.UpdateError("Error while executing pip install", (stdout, stderr)) logger.debug(u"Target: %s, executing pip install %s --ignore-reinstalled --force-reinstall --no-deps" % (target, install_arg)) pip_args += ["--ignore-installed", "--force-reinstall", "--no-deps"] - pip_caller.execute(*pip_args) + returncode, stdout, stderr = pip_caller.execute(*pip_args) + if returncode != 0: + raise exceptions.UpdateError("Error while executing pip install --force-reinstall", (stdout, stderr)) return "ok" From 0c44762f6fbf88f8f205032182f743e8fb937dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 11 Aug 2015 12:35:35 +0200 Subject: [PATCH 08/58] Fix: Better error handling for webassets + cache (cherry picked from commit 38be47c) --- src/octoprint/server/__init__.py | 11 ++++------- src/octoprint/server/util/flask.py | 29 ++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index b70655cf1..4c95f542f 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -739,13 +739,10 @@ def _setup_assets(self): for entry in ("webassets", ".webassets-cache"): path = os.path.join(base_folder, entry) self._logger.debug("Deleting {path}...".format(**locals())) - if os.path.isdir(path): - shutil.rmtree(path, ignore_errors=True) - elif os.path.isfile(path): - try: - os.remove(path) - except: - self._logger.exception("Exception while trying to delete {entry} from {base_folder}".format(**locals())) + shutil.rmtree(path, ignore_errors=True) + self._logger.debug("Creating {path}...".format(**locals())) + os.makedirs(path) + self._logger.info("Reset webasset folder {path}...".format(**locals())) AdjustedEnvironment = type(Environment)(Environment.__name__, (Environment,), dict( resolver_class=util.flask.PluginAssetResolver diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index 48b027d4a..681e08985 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -123,12 +123,19 @@ def fixed_get_translations(): def fix_webassets_cache(): from webassets import cache - import os - import tempfile - import pickle - import shutil + + error_logger = logging.getLogger(__name__ + ".fix_webassets_cache") def fixed_set(self, key, data): + import os + import tempfile + import pickle + import shutil + + if not os.path.exists(self.directory): + error_logger.warn("Cache directory {} doesn't exist, not going " + "to attempt to write cache file".format(self.directory)) + md5 = '%s' % cache.make_md5(self.V, key) filename = os.path.join(self.directory, md5) fd, temp_filename = tempfile.mkstemp(prefix='.' + md5, @@ -148,6 +155,11 @@ def fixed_get(self, key): import warnings from webassets.cache import make_md5 + if not os.path.exists(self.directory): + error_logger.warn("Cache directory {} doesn't exist, not going " + "to attempt to read cache file".format(self.directory)) + return None + try: hash = make_md5(self.V, key) except IOError as e: @@ -194,12 +206,15 @@ def fixed_wrap_cache(self, key, func): try: content = func().getvalue() if self.cache: - log.debug('Storing result in cache with key %s', key,) - self.cache.set(key, content) + try: + log.debug('Storing result in cache with key %s', key,) + self.cache.set(key, content) + except: + error_logger.exception("Got an exception while trying to save file to cache, not caching") return MemoryHunk(content) except: error_logger.exception("Got an exception while trying to apply filter, ignoring file") - return MemoryHunk("") + return MemoryHunk(u"") FilterTool._wrap_cache = fixed_wrap_cache From c538604cd57c2238556215c5e6b81a5dfec4e77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 11 Aug 2015 14:04:52 +0200 Subject: [PATCH 09/58] Added tooltip for additional data in file list Also fixed tooltips to those buttons instead of the icons within the buttons. Fixes #986 (cherry picked from commit 8772162) --- src/octoprint/templates/sidebar/files.jinja2 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/octoprint/templates/sidebar/files.jinja2 b/src/octoprint/templates/sidebar/files.jinja2 index 66adac4a6..2935855b8 100644 --- a/src/octoprint/templates/sidebar/files.jinja2 +++ b/src/octoprint/templates/sidebar/files.jinja2 @@ -10,11 +10,11 @@
{{ _('Size') }}:
-
- -
-
-
+
+ +
+
+
@@ -23,9 +23,9 @@
{{ _('Uploaded') }}:
{{ _('Size') }}:
- -
-
+ +
+
From 202f93426a1d43cb283ceaa1d5653cca50ab7225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 24 Jul 2015 12:14:00 +0200 Subject: [PATCH 10/58] Capture compilation errors on additional templates Shouldn't cause the whole page not to render but log an error. Should provide more resilience against bad plugins. (cherry picked from commit d25202d) --- src/octoprint/server/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/octoprint/server/views.py b/src/octoprint/server/views.py index 4df458bb7..fd6f62b8f 100644 --- a/src/octoprint/server/views.py +++ b/src/octoprint/server/views.py @@ -319,6 +319,8 @@ def _process_template_configs(name, implementation, configs, rules): app.jinja_env.get_or_select_template(data["template"]) except TemplateNotFound: pass + except: + _logger.exception("Error in template {}, not going to include it".format(data["template"])) else: includes[template_type].append(rule["to_entry"](data)) From 4b8bdd244e17716b9304fb98629a5cc943372b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 24 Jul 2015 14:27:02 +0200 Subject: [PATCH 11/58] Improved settings dialog behaviour * Show when settings are saving * Show when settings are being retrieved * Disable Send/Cancel buttons while settings are saving * Prevent concurrent retrieval (cherry picked from commit 88eb124) --- .../static/js/app/viewmodels/settings.js | 51 +++++++++++++++++-- .../templates/dialogs/settings.jinja2 | 4 +- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/octoprint/static/js/app/viewmodels/settings.js b/src/octoprint/static/js/app/viewmodels/settings.js index c860402f0..da93abbc6 100644 --- a/src/octoprint/static/js/app/viewmodels/settings.js +++ b/src/octoprint/static/js/app/viewmodels/settings.js @@ -6,6 +6,10 @@ $(function() { self.users = parameters[1]; self.printerProfiles = parameters[2]; + self.receiving = ko.observable(false); + self.sending = ko.observable(false); + self.callbacks = []; + self.api_enabled = ko.observable(undefined); self.api_key = ko.observable(undefined); self.api_allowCrossOrigin = ko.observable(undefined); @@ -257,13 +261,37 @@ $(function() { }; self.requestData = function(callback) { + if (self.receiving()) { + if (callback) { + self.callbacks.push(callback); + } + return; + } + + self.receiving(true); $.ajax({ url: API_BASEURL + "settings", type: "GET", dataType: "json", success: function(response) { - self.fromResponse(response); - if (callback) callback(); + var callbacks = self.callbacks; + self.callbacks = []; + + if (callback) { + callbacks.push(callback); + } + + try { + self.fromResponse(response); + _.each(callbacks, function(cb) { + cb(); + }); + } finally { + self.receiving(false); + } + }, + error: function(xhr) { + self.receiving(false); } }); }; @@ -421,6 +449,8 @@ $(function() { self.settingsDialog.trigger("beforeSave"); if (data == undefined) { + // we only set sending to true when we didn't include data + self.sending(true); data = ko.mapping.toJS(self.settings); data = _.extend(data, { @@ -510,11 +540,24 @@ $(function() { contentType: "application/json; charset=UTF-8", data: JSON.stringify(data), success: function(response) { - self.fromResponse(response); - if (successCallback) successCallback(response); + self.receiving(true); + self.sending(false); + try { + self.fromResponse(response); + if (successCallback) successCallback(response); + } finally { + self.receiving(false); + } + }, + error: function(xhr) { + self.sending(false); } }); }; + + self.onEventSettingsUpdated = function() { + self.requestData(); + }; } OCTOPRINT_VIEWMODELS.push([ diff --git a/src/octoprint/templates/dialogs/settings.jinja2 b/src/octoprint/templates/dialogs/settings.jinja2 index aa27a6265..f3d3f708c 100644 --- a/src/octoprint/templates/dialogs/settings.jinja2 +++ b/src/octoprint/templates/dialogs/settings.jinja2 @@ -1,7 +1,7 @@