From 2efc5c4fdbd2e5b760dd7e20755afd29c8ba467f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Sep 2015 08:14:35 +0200 Subject: [PATCH 01/21] Ignore update definitions that are lacking the type Caused a KeyError so far, update definitions that are broken like that will now just be ignored instead. Closes #1057 --- .../plugins/softwareupdate/__init__.py | 32 +++---------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/src/octoprint/plugins/softwareupdate/__init__.py b/src/octoprint/plugins/softwareupdate/__init__.py index 14f277a8..2bb0b72f 100644 --- a/src/octoprint/plugins/softwareupdate/__init__.py +++ b/src/octoprint/plugins/softwareupdate/__init__.py @@ -416,14 +416,13 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, if not target in check_targets: continue - populated_check = self._populated_check(target, check) - try: + populated_check = self._populated_check(target, check) target_information, target_update_available, target_update_possible = self._get_current_version(target, populated_check, force=force) if target_information is None: target_information = dict() except exceptions.UnknownCheckType: - self._logger.warn("Unknown update check type for %s" % target) + self._logger.warn("Unknown update check type for target {}".format(target)) continue target_information = dict_merge(dict(local=dict(name="unknown", value="unknown"), remote=dict(name="unknown", value="unknown")), target_information) @@ -669,6 +668,9 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, raise exceptions.RestartFailed() def _populated_check(self, target, check): + if not "type" in check: + raise exceptions.UnknownCheckType() + result = dict(check) if target == "octoprint": @@ -703,30 +705,6 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, def _send_client_message(self, message_type, data=None): self._plugin_manager.send_plugin_message(self._identifier, dict(type=message_type, data=data)) - def _populated_check(self, target, check): - result = dict(check) - - if target == "octoprint": - from flask.ext.babel import gettext - result["displayName"] = check.get("displayName", gettext("OctoPrint")) - result["displayVersion"] = check.get("displayVersion", "{octoprint_version}") - - from octoprint._version import get_versions - versions = get_versions() - if check["type"] == "github_commit": - result["current"] = versions.get("full-revisionid", versions.get("full", "unknown")) - else: - result["current"] = versions["version"] - else: - result["displayName"] = check.get("displayName", target) - result["displayVersion"] = check.get("displayVersion", check.get("current", "unknown")) - if check["type"] in ("github_commit"): - result["current"] = check.get("current", None) - else: - result["current"] = check.get("current", check.get("displayVersion", None)) - - return result - def _get_version_checker(self, target, check): """ Retrieves the version checker to use for given target and check configuration. Will raise an UnknownCheckType From cd7ac032f454f3182e9bd1aa7b45dc041e0e13fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 9 Sep 2015 16:13:10 +0200 Subject: [PATCH 02/21] Fixed an issue that cause user sessions to not be properly associated Sessions could get duplicated, wrongly saved etc. The reason was not persisting the actual user object to the internal session map (but the LocalProxy instead). That could lead to multiple sessions being created for one login, or the session user being set to an anonymous user, or various other odd effects depending on timing. (cherry picked from commit 8aeac51) --- src/octoprint/server/util/flask.py | 6 ++++-- src/octoprint/users.py | 24 +++++++++++++++--------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index 30136222..b67a0908 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -227,8 +227,10 @@ def passive_login(): user = flask.ext.login.current_user if user is not None and not user.is_anonymous(): - flask.g.user = user flask.ext.principal.identity_changed.send(flask.current_app._get_current_object(), identity=flask.ext.principal.Identity(user.get_id())) + if hasattr(user, "get_session"): + flask.session["usersession.id"] = user.get_session() + flask.g.user = user return flask.jsonify(user.asDict()) elif settings().getBoolean(["accessControl", "autologinLocal"]) \ and settings().get(["accessControl", "autologinAs"]) is not None \ @@ -252,7 +254,7 @@ def passive_login(): logger = logging.getLogger(__name__) logger.exception("Could not autologin user %s for networks %r" % (autologinAs, localNetworks)) - return ("", 204) + return "", 204 #~~ cache decorator for cacheable views diff --git a/src/octoprint/users.py b/src/octoprint/users.py index fc5be2b7..7c40d2d8 100644 --- a/src/octoprint/users.py +++ b/src/octoprint/users.py @@ -28,13 +28,18 @@ class UserManager(object): def login_user(self, user): self._cleanup_sessions() - if user is None \ - or (isinstance(user, LocalProxy) and not isinstance(user._get_current_object(), User)) \ - or (not isinstance(user, LocalProxy) and not isinstance(user, User)): + if user is None: + return + + if isinstance(user, LocalProxy): + user = user._get_current_object() + + if not isinstance(user, User): return None if not isinstance(user, SessionUser): user = SessionUser(user) + self._session_users_by_session[user.get_session()] = user if not user.get_name() in self._session_users_by_username: @@ -49,6 +54,9 @@ class UserManager(object): if user is None: return + if isinstance(user, LocalProxy): + user = user._get_current_object() + if not isinstance(user, SessionUser): return @@ -146,12 +154,10 @@ class UserManager(object): del self._session_users_by_username[username] def findUser(self, username=None, session=None): - if session is not None: - for session in self._session_users_by_session: - user = self._session_users_by_session[session] - if username is None or username == user.get_name(): - return user - break + if session is not None and session in self._session_users_by_session: + user = self._session_users_by_session[session] + if username is None or username == user.get_id(): + return user return None From 7021b9fe89439a97240e4d6d68e9563dfdb359ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Sep 2015 08:34:05 +0200 Subject: [PATCH 03/21] User user id, not user name, for all user operations --- src/octoprint/server/__init__.py | 6 ++--- src/octoprint/users.py | 40 +++++++++++++++++--------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index a64b5109..d1be80d0 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -82,7 +82,7 @@ def on_identity_loaded(sender, identity): if user is None: return - identity.provides.add(UserNeed(user.get_name())) + identity.provides.add(UserNeed(user.get_id())) if user.is_user(): identity.provides.add(RoleNeed("user")) if user.is_admin(): @@ -99,9 +99,9 @@ def load_user(id): if userManager is not None: if sessionid: - return userManager.findUser(username=id, session=sessionid) + return userManager.findUser(userid=id, session=sessionid) else: - return userManager.findUser(username=id) + return userManager.findUser(userid=id) return users.DummyUser() diff --git a/src/octoprint/users.py b/src/octoprint/users.py index 8e48f5b0..250f9bc9 100644 --- a/src/octoprint/users.py +++ b/src/octoprint/users.py @@ -23,7 +23,7 @@ class UserManager(object): def __init__(self): self._logger = logging.getLogger(__name__) self._session_users_by_session = dict() - self._session_users_by_username = dict() + self._session_users_by_userid = dict() def login_user(self, user): self._cleanup_sessions() @@ -42,9 +42,10 @@ class UserManager(object): self._session_users_by_session[user.get_session()] = user - if not user.get_name() in self._session_users_by_username: - self._session_users_by_username[user.get_name()] = [] - self._session_users_by_username[user.get_name()].append(user) + userid = user.get_id() + if not userid in self._session_users_by_userid: + self._session_users_by_userid[userid] = [] + self._session_users_by_userid[userid].append(user) self._logger.debug("Logged in user: %r" % user) @@ -60,11 +61,12 @@ class UserManager(object): if not isinstance(user, SessionUser): return - if user.get_name() in self._session_users_by_username: - users_by_username = self._session_users_by_username[user.get_name()] - for u in users_by_username: + userid = user.get_id() + if userid in self._session_users_by_userid: + users_by_userid = self._session_users_by_userid[userid] + for u in users_by_userid: if u.get_session() == user.get_session(): - users_by_username.remove(u) + users_by_userid.remove(u) break if user.get_session() in self._session_users_by_session: @@ -145,18 +147,18 @@ class UserManager(object): pass def removeUser(self, username): - if username in self._session_users_by_username: - users = self._session_users_by_username[username] + if username in self._session_users_by_userid: + users = self._session_users_by_userid[username] sessions = [user.get_session() for user in users if isinstance(user, SessionUser)] for session in sessions: if session in self._session_users_by_session: del self._session_users_by_session[session] - del self._session_users_by_username[username] + del self._session_users_by_userid[username] - def findUser(self, username=None, session=None): + def findUser(self, userid=None, session=None): if session is not None and session in self._session_users_by_session: user = self._session_users_by_session[session] - if username is None or username == user.get_id(): + if userid is None or userid == user.get_id(): return user return None @@ -351,16 +353,16 @@ class FilebasedUserManager(UserManager): self._dirty = True self._save() - def findUser(self, username=None, apikey=None, session=None): - user = UserManager.findUser(self, username=username, session=session) + def findUser(self, userid=None, apikey=None, session=None): + user = UserManager.findUser(self, userid=userid, session=session) if user is not None: return user - if username is not None: - if username not in self._users.keys(): + if userid is not None: + if userid not in self._users.keys(): return None - return self._users[username] + return self._users[userid] elif apikey is not None: for user in self._users.values(): @@ -419,7 +421,7 @@ class User(UserMixin): return self._passwordHash == passwordHash def get_id(self): - return self._username + return self.get_name() def get_name(self): return self._username From 5c9b507cb7985f2adae3c822b390a7ef4982efbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Sep 2015 08:34:05 +0200 Subject: [PATCH 04/21] User user id, not user name, for all user operations (cherry picked from commit 7021b9f) --- src/octoprint/server/__init__.py | 6 ++--- src/octoprint/users.py | 40 +++++++++++++++++--------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 18ea638d..646e573d 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -81,7 +81,7 @@ def on_identity_loaded(sender, identity): if user is None: return - identity.provides.add(UserNeed(user.get_name())) + identity.provides.add(UserNeed(user.get_id())) if user.is_user(): identity.provides.add(RoleNeed("user")) if user.is_admin(): @@ -98,9 +98,9 @@ def load_user(id): if userManager is not None: if sessionid: - return userManager.findUser(username=id, session=sessionid) + return userManager.findUser(userid=id, session=sessionid) else: - return userManager.findUser(username=id) + return userManager.findUser(userid=id) return users.DummyUser() diff --git a/src/octoprint/users.py b/src/octoprint/users.py index 7c40d2d8..76bf23bd 100644 --- a/src/octoprint/users.py +++ b/src/octoprint/users.py @@ -23,7 +23,7 @@ class UserManager(object): def __init__(self): self._logger = logging.getLogger(__name__) self._session_users_by_session = dict() - self._session_users_by_username = dict() + self._session_users_by_userid = dict() def login_user(self, user): self._cleanup_sessions() @@ -42,9 +42,10 @@ class UserManager(object): self._session_users_by_session[user.get_session()] = user - if not user.get_name() in self._session_users_by_username: - self._session_users_by_username[user.get_name()] = [] - self._session_users_by_username[user.get_name()].append(user) + userid = user.get_id() + if not userid in self._session_users_by_userid: + self._session_users_by_userid[userid] = [] + self._session_users_by_userid[userid].append(user) self._logger.debug("Logged in user: %r" % user) @@ -60,11 +61,12 @@ class UserManager(object): if not isinstance(user, SessionUser): return - if user.get_name() in self._session_users_by_username: - users_by_username = self._session_users_by_username[user.get_name()] - for u in users_by_username: + userid = user.get_id() + if userid in self._session_users_by_userid: + users_by_userid = self._session_users_by_userid[userid] + for u in users_by_userid: if u.get_session() == user.get_session(): - users_by_username.remove(u) + users_by_userid.remove(u) break if user.get_session() in self._session_users_by_session: @@ -145,18 +147,18 @@ class UserManager(object): pass def removeUser(self, username): - if username in self._session_users_by_username: - users = self._session_users_by_username[username] + if username in self._session_users_by_userid: + users = self._session_users_by_userid[username] sessions = [user.get_session() for user in users if isinstance(user, SessionUser)] for session in sessions: if session in self._session_users_by_session: del self._session_users_by_session[session] - del self._session_users_by_username[username] + del self._session_users_by_userid[username] - def findUser(self, username=None, session=None): + def findUser(self, userid=None, session=None): if session is not None and session in self._session_users_by_session: user = self._session_users_by_session[session] - if username is None or username == user.get_id(): + if userid is None or userid == user.get_id(): return user return None @@ -351,16 +353,16 @@ class FilebasedUserManager(UserManager): self._dirty = True self._save() - def findUser(self, username=None, apikey=None, session=None): - user = UserManager.findUser(self, username=username, session=session) + def findUser(self, userid=None, apikey=None, session=None): + user = UserManager.findUser(self, userid=userid, session=session) if user is not None: return user - if username is not None: - if username not in self._users.keys(): + if userid is not None: + if userid not in self._users.keys(): return None - return self._users[username] + return self._users[userid] elif apikey is not None: for user in self._users.values(): @@ -419,7 +421,7 @@ class User(UserMixin): return self._passwordHash == passwordHash def get_id(self): - return self._username + return self.get_name() def get_name(self): return self._username From b56ba6589c9710543d2796c1ce95acd6152b886d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Sep 2015 08:14:35 +0200 Subject: [PATCH 05/21] Ignore update definitions that are lacking the type Caused a KeyError so far, update definitions that are broken like that will now just be ignored instead. Closes #1057 (cherry picked from commit 2efc5c4) --- src/octoprint/plugins/softwareupdate/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/octoprint/plugins/softwareupdate/__init__.py b/src/octoprint/plugins/softwareupdate/__init__.py index ad1683a1..399a70a1 100644 --- a/src/octoprint/plugins/softwareupdate/__init__.py +++ b/src/octoprint/plugins/softwareupdate/__init__.py @@ -400,14 +400,13 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, if not target in check_targets: continue - populated_check = self._populated_check(target, check) - try: + populated_check = self._populated_check(target, check) target_information, target_update_available, target_update_possible = self._get_current_version(target, populated_check, force=force) if target_information is None: target_information = dict() except exceptions.UnknownCheckType: - self._logger.warn("Unknown update check type for %s" % target) + self._logger.warn("Unknown update check type for target {}".format(target)) continue target_information = dict_merge(dict(local=dict(name="unknown", value="unknown"), remote=dict(name="unknown", value="unknown")), target_information) @@ -655,6 +654,9 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, raise exceptions.RestartFailed() def _populated_check(self, target, check): + if not "type" in check: + raise exceptions.UnknownCheckType() + result = dict(check) if target == "octoprint": From b6053c14f99bc9695e286916799db69ba5f47d25 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig <1hellwig@informatik.uni-hamburg.de> Date: Fri, 11 Sep 2015 11:20:41 +0200 Subject: [PATCH 06/21] add errorhandler decorator to blueprintplugin This will allow you to add an errorhandler for your blueprint easily. --- src/octoprint/plugin/types.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/octoprint/plugin/types.py b/src/octoprint/plugin/types.py index bbd06f54..66c8bcff 100644 --- a/src/octoprint/plugin/types.py +++ b/src/octoprint/plugin/types.py @@ -1055,6 +1055,24 @@ class BlueprintPlugin(OctoPrintPlugin, RestartNeedingPlugin): return f return decorator + @staticmethod + def errorhandler(code_or_exception): + """ + A decorator to mark errorhandlings methods in your BlueprintPlugin subclass. Works just the same as Flask's + own ``errorhandler`` decorator available on blueprints. + + See `the documentation for flask.Blueprint.errorhandler `_ + and `the documentation for flask.Flask.errorhandler `_ for more + information. + """ + from collections import defaultdict + def decorator(f): + if not hasattr(f, "_blueprint_error_handler") or f._blueprint_error_handler is None: + f._blueprint_error_handler = defaultdict(list) + f._blueprint_error_handler[f.__name__].append(code_or_exception) + return f + return decorator + def get_blueprint(self): """ Creates and returns the blueprint for your plugin. Override this if you want to define and handle your blueprint yourself. @@ -1073,6 +1091,9 @@ class BlueprintPlugin(OctoPrintPlugin, RestartNeedingPlugin): for blueprint_rule in f._blueprint_rules[member]: rule, options = blueprint_rule blueprint.add_url_rule(rule, options.pop("endpoint", f.__name__), view_func=f, **options) + if hasattr(f, "_blueprint_error_handler") and member in f._blueprint_error_handler: + for code_or_exception in f._blueprint_error_handler[member]: + blueprint.errorhandler(code_or_exception)(f) return blueprint def get_blueprint_kwargs(self): From 2c0eed266ceadeb3cd9e263d2170b9e118ce726a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Sep 2015 11:48:38 +0200 Subject: [PATCH 07/21] Fixed some wrong indentation that somehow made it through editorconfig --- src/octoprint/plugin/types.py | 2 +- .../plugins/virtual_printer/__init__.py | 50 +++++++++++-------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/octoprint/plugin/types.py b/src/octoprint/plugin/types.py index 66c8bcff..c7acb288 100644 --- a/src/octoprint/plugin/types.py +++ b/src/octoprint/plugin/types.py @@ -80,7 +80,7 @@ class OctoPrintPlugin(Plugin): and if not creating it before returning it. Injected by the plugin core system upon initialization of the implementation. - .. automethod:: get_plugin_data_folder + .. automethod:: get_plugin_data_folder """ def get_plugin_data_folder(self): diff --git a/src/octoprint/plugins/virtual_printer/__init__.py b/src/octoprint/plugins/virtual_printer/__init__.py index ab66badd..e915aa00 100644 --- a/src/octoprint/plugins/virtual_printer/__init__.py +++ b/src/octoprint/plugins/virtual_printer/__init__.py @@ -7,25 +7,34 @@ __copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms import octoprint.plugin + class VirtualPrinterPlugin(octoprint.plugin.SettingsPlugin): + def virtual_printer_factory(self, comm_instance, port, baudrate, + read_timeout): + if not port == "VIRTUAL": + return None - def virtual_printer_factory(self, comm_instance, port, baudrate, read_timeout): - if not port == "VIRTUAL": - return None + if not self._settings.global_get_boolean( + ["devel", "virtualPrinter", "enabled"]): + return None - if not self._settings.global_get_boolean(["devel", "virtualPrinter", "enabled"]): - return None + import logging + import logging.handlers - import logging - import logging.handlers + seriallog_handler = logging.handlers.RotatingFileHandler( + self._settings.get_plugin_logfile_path(postfix="serial"), + maxBytes=2 * 1024 * 1024) + seriallog_handler.setFormatter( + logging.Formatter("%(asctime)s %(message)s")) + seriallog_handler.setLevel(logging.DEBUG) - seriallog_handler = logging.handlers.RotatingFileHandler(self._settings.get_plugin_logfile_path(postfix="serial"), maxBytes=2*1024*1024) - seriallog_handler.setFormatter(logging.Formatter("%(asctime)s %(message)s")) - seriallog_handler.setLevel(logging.DEBUG) + from . import virtual + + serial_obj = virtual.VirtualPrinter( + seriallog_handler=seriallog_handler, + read_timeout=float(read_timeout)) + return serial_obj - from . import virtual - serial_obj = virtual.VirtualPrinter(seriallog_handler=seriallog_handler, read_timeout=float(read_timeout)) - return serial_obj __plugin_name__ = "Virtual Printer" __plugin_author__ = "Gina Häußge, based on work by Daid Braam" @@ -33,13 +42,14 @@ __plugin_homepage__ = "https://github.com/foosel/OctoPrint/wiki/Plugin:-Virtual- __plugin_license__ = "AGPLv3" __plugin_description__ = "Provides a virtual printer via a virtual serial port for development and testing purposes" + def __plugin_load__(): - plugin = VirtualPrinterPlugin() + plugin = VirtualPrinterPlugin() - global __plugin_implementation__ - __plugin_implementation__ = plugin + global __plugin_implementation__ + __plugin_implementation__ = plugin - global __plugin_hooks__ - __plugin_hooks__ = { - "octoprint.comm.transport.serial.factory": plugin.virtual_printer_factory - } + global __plugin_hooks__ + __plugin_hooks__ = { + "octoprint.comm.transport.serial.factory": plugin.virtual_printer_factory + } From ef06c511543639faa6cb37f4fc610e2d6b0480e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 11 Sep 2015 11:53:33 +0200 Subject: [PATCH 08/21] Added @punkkeks to AUTHORS.md --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 62af6002..412f9077 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -54,6 +54,7 @@ date of first contribution): * [Nicanor Romero Venier](https://github.com/nicanor-romero) * [Thomas Hou](https://github.com/masterhou) * [Mark Bastiaans](https://github.com/markbastiaans) + * [Marcel Hellwig](https://github.com/punkkeks) 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 8af8b8f79a505b14fe8dc78a6e52878e7689d4c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sat, 12 Sep 2015 11:09:28 +0200 Subject: [PATCH 09/21] SWU: Track check origins, ignore if from unavailable plugin There was a problem with software update checks configurations stored in config.yaml for which the providing plugin was then removed, since those check definitions then lacked their default values to be merged on whatever was stored in config.yaml, causing incomplete check configurations as a consequence over which the plugin tripped. This patch fixes that in that it tracks which check config keys are provided by plugins and only returns those as the active check configurations that belong to plugins that are still in the system. TODO: This is only half of the solution. Check configurations of plugins that are being uninstalled should be removed from the config if the user decides to remove any settings by the plugin too. We need some adjustments in the lifecycle tracking in order to make this possible however, so for now this must suffice to at least prevent any errors from occuring when incomplete configs are encountered. --- .../plugins/softwareupdate/__init__.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/octoprint/plugins/softwareupdate/__init__.py b/src/octoprint/plugins/softwareupdate/__init__.py index 2bb0b72f..33451e57 100644 --- a/src/octoprint/plugins/softwareupdate/__init__.py +++ b/src/octoprint/plugins/softwareupdate/__init__.py @@ -75,6 +75,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, self._refresh_configured_checks = False self._configured_checks = self._settings.get(["checks"], merged=True) update_check_hooks = self._plugin_manager.get_hooks("octoprint.plugin.softwareupdate.check_config") + check_providers = self._settings.get(["check_providers"], merged=True) for name, hook in update_check_hooks.items(): try: hook_checks = hook() @@ -82,9 +83,23 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, self._logger.exception("Error while retrieving update information from plugin {name}".format(**locals())) else: for key, data in hook_checks.items(): + check_providers[key] = name if key in self._configured_checks: data = dict_merge(data, self._configured_checks[key]) self._configured_checks[key] = data + self._settings.set(["check_providers"], check_providers) + self._settings.save() + + # we only want to process checks that came from plugins for + # which the plugins are still installed and enabled + config_checks = self._settings.get(["checks"]) + plugin_and_not_enabled = lambda k: k in check_providers and \ + not check_providers[k] in self._plugin_manager.enabled_plugins + obsolete_plugin_checks = filter(plugin_and_not_enabled, + config_checks.keys()) + for key in obsolete_plugin_checks: + self._logger.debug("Check for key {} was provided by plugin {} that's no longer available, ignoring it".format(key, check_providers[key])) + del self._configured_checks[key] return self._configured_checks @@ -150,6 +165,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, }, }, "pip_command": None, + "check_providers": {}, "cache_ttl": 24 * 60, } @@ -422,7 +438,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, if target_information is None: target_information = dict() except exceptions.UnknownCheckType: - self._logger.warn("Unknown update check type for target {}".format(target)) + self._logger.warn("Unknown update check type for target {}: {}".format(target, check.get("type", ""))) continue target_information = dict_merge(dict(local=dict(name="unknown", value="unknown"), remote=dict(name="unknown", value="unknown")), target_information) From 0253f525b0a57433fceefcd108614eb4f08c12ce Mon Sep 17 00:00:00 2001 From: Bryan Mayland Date: Sat, 12 Sep 2015 11:32:52 -0400 Subject: [PATCH 10/21] Include a time estimate for gcode which uses firmware retract, if retract settings are seen in gcode --- src/octoprint/util/gcodeInterpreter.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/octoprint/util/gcodeInterpreter.py b/src/octoprint/util/gcodeInterpreter.py index b0b69b66..7ac7508f 100644 --- a/src/octoprint/util/gcodeInterpreter.py +++ b/src/octoprint/util/gcodeInterpreter.py @@ -54,6 +54,9 @@ class gcode(object): absoluteE = True scale = 1.0 posAbs = True + fwretractTime = 0 + fwretractDist = 0 + fwrecoverTime = 0 feedRateXY = min(printer_profile["axes"]["x"]["speed"], printer_profile["axes"]["y"]["speed"]) if feedRateXY == 0: # some somewhat sane default if axes speeds are insane... @@ -172,6 +175,10 @@ class gcode(object): P = getCodeFloat(line, 'P') if P is not None: totalMoveTimeMinute += P / 60.0 / 1000.0 + elif G == 10: #Firmware retract + totalMoveTimeMinute += fwretractTime + elif G == 11: #Firmware retract recover + totalMoveTimeMinute += fwrecoverTime elif G == 20: #Units are inches scale = 25.4 elif G == 21: #Units are mm @@ -214,6 +221,15 @@ class gcode(object): absoluteE = True elif M == 83: #Relative E absoluteE = False + elif M == 207 or M == 208: #Firmware retract settings + s = getCodeFloat(line, 'S') + f = getCodeFloat(line, 'F') + if s is not None and f is not None: + if M == 207: + fwretractTime = s / f + fwretractDist = s + else: + fwrecoverTime = (fwretractDist + s) / f elif T is not None: if T > settings().getInt(["gcodeAnalysis", "maxExtruders"]): From e46e7b3ee2ec4dad6346decbacd504ac814863b4 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig <1hellwig@informatik.uni-hamburg.de> Date: Thu, 17 Sep 2015 18:04:14 +0200 Subject: [PATCH 11/21] changed magic line from python->python2 Since python is sometimes (and for me on arch linux) a symlink to the python3 utils, you should be more explicit with that. --- run | 2 +- setup.py | 2 +- src/octoprint/__init__.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/run b/run index 0c51da39..1145a24f 100755 --- a/run +++ b/run @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 import os import sys diff --git a/setup.py b/setup.py index fc2a74d6..5130635b 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 # coding=utf-8 from setuptools import setup, find_packages diff --git a/src/octoprint/__init__.py b/src/octoprint/__init__.py index 67baa4f7..883acc5d 100644 --- a/src/octoprint/__init__.py +++ b/src/octoprint/__init__.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 + import sys from octoprint.daemon import Daemon from octoprint.server import Server From 119f3ba45bf59e4d7304098731d546b2ea3ce44d Mon Sep 17 00:00:00 2001 From: Andres Date: Sun, 20 Sep 2015 17:44:49 -0300 Subject: [PATCH 12/21] Fixed wrong urls --- docs/api/job.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api/job.rst b/docs/api/job.rst index 3bcc1fde..eeb3b071 100644 --- a/docs/api/job.rst +++ b/docs/api/job.rst @@ -37,7 +37,7 @@ Issue a job command .. sourcecode:: http - POST /api/control/job HTTP/1.1 + POST /api/job HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... @@ -54,7 +54,7 @@ Issue a job command .. sourcecode:: http - POST /api/control/job HTTP/1.1 + POST /api/job HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... @@ -71,7 +71,7 @@ Issue a job command .. sourcecode:: http - POST /api/control/job HTTP/1.1 + POST /api/job HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... @@ -88,7 +88,7 @@ Issue a job command .. sourcecode:: http - POST /api/control/job HTTP/1.1 + POST /api/job HTTP/1.1 Host: example.com Content-Type: application/json X-Api-Key: abcdef... From d2e2cb814f3901a50803dd3adc6bc53e2a6f793b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 21 Sep 2015 08:41:28 +0200 Subject: [PATCH 13/21] M400 and proper M114 in virtual printer --- src/octoprint/plugins/virtual_printer/virtual.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/octoprint/plugins/virtual_printer/virtual.py b/src/octoprint/plugins/virtual_printer/virtual.py index a6d5399e..e9becc5b 100644 --- a/src/octoprint/plugins/virtual_printer/virtual.py +++ b/src/octoprint/plugins/virtual_printer/virtual.py @@ -235,7 +235,7 @@ class VirtualPrinter(object): self._deleteSdFile(filename) elif "M114" in data: # send dummy position report - output = "C: X:10.00 Y:3.20 Z:5.20 E:1.24" + output = "C: X:{} Y:{} Z:{} E:{}".format(self._lastX, self._lastY, self._lastZ, self._lastE) if not self._okBeforeCommandOutput: output = "ok " + output self._send(output) @@ -243,6 +243,8 @@ class VirtualPrinter(object): elif "M117" in data: # we'll just use this to echo a message, to allow playing around with pause triggers self._send("echo:%s" % re.search("M117\s+(.*)", data).group(1)) + elif "M400" in data: + self.buffered.join() elif "M999" in data: # mirror Marlin behaviour self._send("Resend: 1") @@ -674,6 +676,7 @@ class VirtualPrinter(object): continue self._performMove(line) + self.buffered.task_done() def write(self, data): if self._debug_drop_connection: From a05e3a44af9d9e72316ba7a798c6b7616a412b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 21 Sep 2015 15:00:06 +0200 Subject: [PATCH 14/21] Updated tornado, sockjs-tornado and psutil dependencies --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 5130635b..3633a628 100644 --- a/setup.py +++ b/setup.py @@ -16,8 +16,8 @@ import octoprint_setuptools INSTALL_REQUIRES = [ "flask>=0.9,<0.11", "werkzeug==0.8.3", - "tornado==4.0.1", - "sockjs-tornado==1.0.1", + "tornado==4.0.2", + "sockjs-tornado==1.0.2", "PyYAML==3.10", "Flask-Login==0.2.2", "Flask-Principal==0.3.5", @@ -34,7 +34,7 @@ INSTALL_REQUIRES = [ "pkginfo==1.2.1", "requests==2.7.0", "semantic_version==2.4.2", - "psutil==3.1.1" + "psutil==3.2.1" ] # Additional requirements for optional install options From 43ca4d8252eda987d1be6bad8b6531558e9f6974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 21 Sep 2015 16:42:11 +0200 Subject: [PATCH 15/21] SWU: Do not overwrite check information again Current version information of OctoPrint from a check definition could be overwritten for checks under certain circumstances. --- src/octoprint/plugins/softwareupdate/__init__.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/octoprint/plugins/softwareupdate/__init__.py b/src/octoprint/plugins/softwareupdate/__init__.py index 399a70a1..bb324b37 100644 --- a/src/octoprint/plugins/softwareupdate/__init__.py +++ b/src/octoprint/plugins/softwareupdate/__init__.py @@ -689,13 +689,6 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, if not "type" in check: raise exceptions.ConfigurationInvalid("no check type defined") - if target == "octoprint": - from octoprint._version import get_versions - from flask.ext.babel import gettext - check["displayName"] = gettext("OctoPrint") - check["displayVersion"] = "{octoprint_version}" - check["current"] = get_versions()["version"] - check_type = check["type"] if check_type == "github_release": return version_checks.github_release From 25a4d4b79b3f3f24a95c475fc56d1cf43324d17a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sat, 12 Sep 2015 11:09:28 +0200 Subject: [PATCH 16/21] SWU: Track check origins, ignore if from unavailable plugin There was a problem with software update checks configurations stored in config.yaml for which the providing plugin was then removed, since those check definitions then lacked their default values to be merged on whatever was stored in config.yaml, causing incomplete check configurations as a consequence over which the plugin tripped. This patch fixes that in that it tracks which check config keys are provided by plugins and only returns those as the active check configurations that belong to plugins that are still in the system. TODO: This is only half of the solution. Check configurations of plugins that are being uninstalled should be removed from the config if the user decides to remove any settings by the plugin too. We need some adjustments in the lifecycle tracking in order to make this possible however, so for now this must suffice to at least prevent any errors from occuring when incomplete configs are encountered. (cherry picked from commit 8af8b8f) --- .../plugins/softwareupdate/__init__.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/octoprint/plugins/softwareupdate/__init__.py b/src/octoprint/plugins/softwareupdate/__init__.py index bb324b37..a3f7d2ef 100644 --- a/src/octoprint/plugins/softwareupdate/__init__.py +++ b/src/octoprint/plugins/softwareupdate/__init__.py @@ -59,6 +59,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, self._refresh_configured_checks = False self._configured_checks = self._settings.get(["checks"], merged=True) update_check_hooks = self._plugin_manager.get_hooks("octoprint.plugin.softwareupdate.check_config") + check_providers = self._settings.get(["check_providers"], merged=True) for name, hook in update_check_hooks.items(): try: hook_checks = hook() @@ -66,9 +67,23 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, self._logger.exception("Error while retrieving update information from plugin {name}".format(**locals())) else: for key, data in hook_checks.items(): + check_providers[key] = name if key in self._configured_checks: data = dict_merge(data, self._configured_checks[key]) self._configured_checks[key] = data + self._settings.set(["check_providers"], check_providers) + self._settings.save() + + # we only want to process checks that came from plugins for + # which the plugins are still installed and enabled + config_checks = self._settings.get(["checks"]) + plugin_and_not_enabled = lambda k: k in check_providers and \ + not check_providers[k] in self._plugin_manager.enabled_plugins + obsolete_plugin_checks = filter(plugin_and_not_enabled, + config_checks.keys()) + for key in obsolete_plugin_checks: + self._logger.debug("Check for key {} was provided by plugin {} that's no longer available, ignoring it".format(key, check_providers[key])) + del self._configured_checks[key] return self._configured_checks @@ -134,6 +149,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, }, }, "pip_command": None, + "check_providers": {}, "cache_ttl": 24 * 60, } @@ -406,7 +422,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, if target_information is None: target_information = dict() except exceptions.UnknownCheckType: - self._logger.warn("Unknown update check type for target {}".format(target)) + self._logger.warn("Unknown update check type for target {}: {}".format(target, check.get("type", ""))) continue target_information = dict_merge(dict(local=dict(name="unknown", value="unknown"), remote=dict(name="unknown", value="unknown")), target_information) From c26515c13d52ab290a275eba4ca492de0dcd8408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 22 Sep 2015 11:35:47 +0200 Subject: [PATCH 17/21] PipCaller: Allow update of used pip command --- src/octoprint/util/pip.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/octoprint/util/pip.py b/src/octoprint/util/pip.py index 980e5735..94b13926 100644 --- a/src/octoprint/util/pip.py +++ b/src/octoprint/util/pip.py @@ -21,14 +21,13 @@ class PipCaller(object): def __init__(self, configured=None): self._logger = logging.getLogger(__name__) - self._configured = configured + self.configured = configured self._command = None self._version = None - self._command, self._version = self._find_pip() + self.trigger_refresh() - self.refresh = False self.on_log_call = lambda *args, **kwargs: None self.on_log_stdout = lambda *args, **kwargs: None self.on_log_stderr = lambda *args, **kwargs: None @@ -57,10 +56,18 @@ class PipCaller(object): def available(self): return self._command is not None + def trigger_refresh(self): + try: + self._command, self._version = self._find_pip() + except: + self._logger.exception("Error while discovering pip command") + self._command = None + self._version = None + self.refresh = False + def execute(self, *args): if self.refresh: - self._command, self._version = self._find_pip() - self.refresh = False + self.trigger_refresh() if self._command is None: raise UnknownPip() @@ -111,7 +118,7 @@ class PipCaller(object): def _find_pip(self): - pip_command = self._configured + pip_command = self.configured pip_version = None if pip_command is None: From 65bc28a03e56275174918f4b25c4c035d84408aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 22 Sep 2015 11:36:57 +0200 Subject: [PATCH 18/21] PMGR: Added configuration dialog and info re used pip binary & version --- .../plugins/pluginmanager/__init__.py | 26 +++++- .../pluginmanager/static/js/pluginmanager.js | 81 +++++++++++++++++-- .../templates/pluginmanager_settings.jinja2 | 56 +++++++++++++ 3 files changed, 155 insertions(+), 8 deletions(-) diff --git a/src/octoprint/plugins/pluginmanager/__init__.py b/src/octoprint/plugins/pluginmanager/__init__.py index c7950f48..8390cd8e 100644 --- a/src/octoprint/plugins/pluginmanager/__init__.py +++ b/src/octoprint/plugins/pluginmanager/__init__.py @@ -87,9 +87,17 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, ) def on_settings_save(self, data): + old_pip = self._settings.get(["pip"]) octoprint.plugin.SettingsPlugin.on_settings_save(self, data) + new_pip = self._settings.get(["pip"]) + self._repository_cache_ttl = self._settings.get_int(["repository_ttl"]) * 60 - self._pip_caller.refresh = True + if old_pip != new_pip: + self._pip_caller.configured = new_pip + try: + self._pip_caller.trigger_refresh() + except: + self._pip_caller ##~~ AssetPlugin @@ -169,7 +177,18 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, if "refresh_repository" in request.values and request.values["refresh_repository"] in valid_boolean_trues: self._repository_available = self._refresh_repository() - return jsonify(plugins=result, repository=dict(available=self._repository_available, plugins=self._repository_plugins), os=self._get_os(), octoprint=self._get_octoprint_version()) + return jsonify(plugins=result, + repository=dict( + available=self._repository_available, + plugins=self._repository_plugins + ), + os=self._get_os(), + octoprint=self._get_octoprint_version(), + pip=dict( + available=self._pip_caller.available, + command=self._pip_caller.command, + version=str(self._pip_caller.version) + )) def on_api_command(self, command, data): if not admin_permission.can(): @@ -603,7 +622,8 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, pending_enable=(not plugin.enabled and plugin.key in self._pending_enable), pending_disable=(plugin.enabled and plugin.key in self._pending_disable), pending_install=(plugin.key in self._pending_install), - pending_uninstall=(plugin.key in self._pending_uninstall) + pending_uninstall=(plugin.key in self._pending_uninstall), + origin=plugin.origin.type ) __plugin_name__ = "Plugin Manager" diff --git a/src/octoprint/plugins/pluginmanager/static/js/pluginmanager.js b/src/octoprint/plugins/pluginmanager/static/js/pluginmanager.js index 7c5265ff..35a634d8 100644 --- a/src/octoprint/plugins/pluginmanager/static/js/pluginmanager.js +++ b/src/octoprint/plugins/pluginmanager/static/js/pluginmanager.js @@ -6,6 +6,12 @@ $(function() { self.settingsViewModel = parameters[1]; self.printerState = parameters[2]; + self.config_repositoryUrl = ko.observable(); + self.config_repositoryTtl = ko.observable(); + self.config_pipCommand = ko.observable(); + + self.configurationDialog = $("#settings_plugin_pluginmanager_configurationdialog"); + self.plugins = new ItemListHelper( "plugin.pluginmanager.installedplugins", { @@ -72,6 +78,10 @@ $(function() { self.followDependencyLinks = ko.observable(false); + self.pipAvailable = ko.observable(false); + self.pipCommand = ko.observable(); + self.pipVersion = ko.observable(); + self.working = ko.observable(false); self.workingTitle = ko.observable(); self.workingDialog = undefined; @@ -86,11 +96,15 @@ $(function() { }; self.enableUninstall = function(data) { - return self.enableManagement() && !data.bundled && data.key != 'pluginmanager' && !data.pending_uninstall; + return self.enableManagement() + && (data.origin != "entry_point" || self.pipAvailable()) + && !data.bundled + && data.key != 'pluginmanager' + && !data.pending_uninstall; }; self.enableRepoInstall = function(data) { - return self.enableManagement() && self.isCompatible(data); + return self.enableManagement() && self.pipAvailable() && self.isCompatible(data); }; self.invalidUrl = ko.computed(function() { @@ -100,7 +114,7 @@ $(function() { self.enableUrlInstall = ko.computed(function() { var url = self.installUrl(); - return self.enableManagement() && url !== undefined && url.trim() != "" && !self.invalidUrl(); + return self.enableManagement() && self.pipAvailable() && url !== undefined && url.trim() != "" && !self.invalidUrl(); }); self.invalidArchive = ko.computed(function() { @@ -110,7 +124,7 @@ $(function() { self.enableArchiveInstall = ko.computed(function() { var name = self.uploadFilename(); - return self.enableManagement() && name !== undefined && name.trim() != "" && !self.invalidArchive(); + return self.enableManagement() && self.pipAvailable() && name !== undefined && name.trim() != "" && !self.invalidArchive(); }); self.uploadElement.fileupload({ @@ -167,7 +181,8 @@ $(function() { self.fromResponse = function(data) { self._fromPluginsResponse(data.plugins); - self._fromRepositoryResponse(data.repository) + self._fromRepositoryResponse(data.repository); + self._fromPipResponse(data.pip); }; self._fromPluginsResponse = function(data) { @@ -188,6 +203,17 @@ $(function() { } }; + self._fromPipResponse = function(data) { + self.pipAvailable(data.available); + if (data.available) { + self.pipCommand(data.command); + self.pipVersion(data.version); + } else { + self.pipCommand(undefined); + self.pipVersion(undefined); + } + }; + self.requestData = function(includeRepo) { if (!self.loginState.isAdmin()) { return; @@ -343,6 +369,51 @@ $(function() { self.requestData(true); }; + self.showPluginSettings = function() { + self._copyConfig(); + self.configurationDialog.modal(); + }; + + self.savePluginSettings = function() { + var pipCommand = self.config_pipCommand(); + if (pipCommand != undefined && pipCommand.trim() == "") { + pipCommand = null; + } + + var repository = self.config_repositoryUrl(); + if (repository != undefined && repository.trim() == "") { + repository = null; + } + + var repositoryTtl; + try { + repositoryTtl = parseInt(self.config_repositoryTtl()); + } catch (ex) { + repositoryTtl = null; + } + + var data = { + plugins: { + pluginmanager: { + repository: repository, + repository_ttl: repositoryTtl, + pip: pipCommand + } + } + }; + self.settingsViewModel.saveData(data, function() { + self.configurationDialog.modal("hide"); + self._copyConfig(); + self.refreshRepository(); + }); + }; + + self._copyConfig = function() { + self.config_repositoryUrl(self.settingsViewModel.settings.plugins.pluginmanager.repository()); + self.config_repositoryTtl(self.settingsViewModel.settings.plugins.pluginmanager.repository_ttl()); + self.config_pipCommand(self.settingsViewModel.settings.plugins.pluginmanager.pip()); + }; + self.installed = function(data) { return _.includes(self.installedPlugins(), data.id); }; diff --git a/src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2 b/src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2 index b855dc81..7c00534a 100644 --- a/src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2 +++ b/src/octoprint/plugins/pluginmanager/templates/pluginmanager_settings.jinja2 @@ -4,7 +4,20 @@ {% endmacro %} +{% macro pluginmanager_nopip() %} +
{% trans %} + The pip command could not be detected automatically, + please configure it manually. No installation and uninstallation of plugin + packages is possible while pip is unavailable. +{% endtrans %}
+{% endmacro %} + {{ pluginmanager_printing() }} +{{ pluginmanager_nopip() }} + +
+ +

{{ _('Installed Plugins') }}

@@ -47,6 +60,10 @@ +

+ Using pip at "" (Version ) +

+ + + + From fd4271a962b8e4b3fb87eb65ceb7bceb48a63a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 22 Sep 2015 11:39:45 +0200 Subject: [PATCH 19/21] PipCaller: Added back missing member variable --- src/octoprint/util/pip.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/octoprint/util/pip.py b/src/octoprint/util/pip.py index 94b13926..7fa3dbc3 100644 --- a/src/octoprint/util/pip.py +++ b/src/octoprint/util/pip.py @@ -22,6 +22,7 @@ class PipCaller(object): self._logger = logging.getLogger(__name__) self.configured = configured + self.refresh = False self._command = None self._version = None From b4b5689bc47264d7a2e7774abb9fd8e9083be670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 22 Sep 2015 15:23:50 +0200 Subject: [PATCH 20/21] Fix: Correctly handle unset Content-Type header for command requests --- src/octoprint/server/util/flask.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index b67a0908..7fe8ae34 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -530,7 +530,8 @@ def get_remote_address(request): def get_json_command_from_request(request, valid_commands): - if not "application/json" in request.headers["Content-Type"]: + content_type = request.headers.get("Content-Type", None) + if content_type is None or not "application/json" in content_type: return None, None, make_response("Expected content-type JSON", 400) data = request.json From 055a5d06f636bfd75109779aefb4f20567b4bac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 23 Sep 2015 13:40:41 +0200 Subject: [PATCH 21/21] Fix: Correctly persist all changes to gcode scripts --- .../static/js/app/viewmodels/settings.js | 63 ++++++++++++------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/src/octoprint/static/js/app/viewmodels/settings.js b/src/octoprint/static/js/app/viewmodels/settings.js index 39b58432..797e3e05 100644 --- a/src/octoprint/static/js/app/viewmodels/settings.js +++ b/src/octoprint/static/js/app/viewmodels/settings.js @@ -540,6 +540,29 @@ $(function() { additionalBaudrates: function() { return _.map(splitTextToArray(self.serial_additionalBaudrates(), ",", true, function(item) { return !isNaN(parseInt(item)); }), function(item) { return parseInt(item); }) }, longRunningCommands: function() { return splitTextToArray(self.serial_longRunningCommands(), ",", true) }, checksumRequiringCommands: function() { return splitTextToArray(self.serial_checksumRequiringCommands(), ",", true) } + }, + scripts: { + gcode: function() { + // we have a special handler function for the gcode scripts since the + // server will always send us those that have been set already, so we + // can't depend on all keys that we support to be present in the + // original request we iterate through in mapFromObservables to + // generate our response - hence we use our observables instead + // + // Note: If we ever introduce sub categories in the gcode scripts + // here (more _ after the prefix), we'll need to adjust this code + // to be able to cope with that, right now it only strips the prefix + // and uses the rest as key in the result, no recursive translation + // is done! + var result = {}; + var prefix = "scripts_gcode_"; + var observables = _.filter(_.keys(self), function(key) { return _.startsWith(key, prefix); }); + _.each(observables, function(observable) { + var script = observable.substring(prefix.length); + result[script] = self[observable](); + }); + return result; + } } }; @@ -554,7 +577,10 @@ $(function() { observable = keyPrefix + "_" + observable; } - if (_.isPlainObject(value)) { + if (mapping && mapping[key] && _.isFunction(mapping[key])) { + result[key] = mapping[key](); + flag = true; + } else if (_.isPlainObject(value)) { // value is another object, we'll dive deeper var subresult = mapFromObservables(value, (mapping && mapping[key]) ? mapping[key] : undefined, observable); if (subresult != undefined) { @@ -562,15 +588,9 @@ $(function() { result[key] = subresult; flag = true; } - } else { - // if we have a custom read function for this, we'll use it - if (mapping && mapping[key] && _.isFunction(mapping[key])) { - result[key] = mapping[key](); - flag = true; - } else if (self.hasOwnProperty(observable)) { - result[key] = self[observable](); - flag = true; - } + } else if (self.hasOwnProperty(observable)) { + result[key] = self[observable](); + flag = true; } }); @@ -647,22 +667,17 @@ $(function() { observable = keyPrefix + "_" + observable; } - if (_.isPlainObject(value)) { + var haveLocalVersion = local && local.hasOwnProperty(key); + + if (mapping && mapping[key] && _.isFunction(mapping[key]) && !haveLocalVersion) { + // if we have a custom apply function for this, we'll use it + mapping[key](value); + } else if (_.isPlainObject(value)) { // value is another object, we'll dive deeper mapToObservables(value, (mapping && mapping[key]) ? mapping[key] : undefined, (local && local[key]) ? local[key] : undefined, observable); - } else { - // if we have a local version of this field, we'll not override it - if (local && local.hasOwnProperty(key)) { - return; - } - - if (mapping && mapping[key] && _.isFunction(mapping[key])) { - // if we have a custom apply function for this, we'll use it - mapping[key](value); - } else if (self.hasOwnProperty(observable)) { - // else if we have a matching observable, we'll use that - self[observable](value); - } + } else if (!haveLocalVersion && self.hasOwnProperty(observable)) { + // if we have a matching observable, we'll use that + self[observable](value); } }); };