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 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... 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..3633a628 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 @@ -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 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 diff --git a/src/octoprint/plugin/types.py b/src/octoprint/plugin/types.py index bbd06f54..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): @@ -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): diff --git a/src/octoprint/plugins/pluginmanager/__init__.py b/src/octoprint/plugins/pluginmanager/__init__.py index 0f07577e..b493ca1a 100644 --- a/src/octoprint/plugins/pluginmanager/__init__.py +++ b/src/octoprint/plugins/pluginmanager/__init__.py @@ -88,9 +88,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 @@ -178,9 +186,17 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, self._repository_available = self._refresh_repository() return jsonify(plugins=self._get_plugins(), - repository=dict(available=self._repository_available, plugins=self._repository_plugins), + repository=dict( + available=self._repository_available, + plugins=self._repository_plugins + ), os=self._get_os(), - octoprint=self._get_octoprint_version_string()) + octoprint=self._get_octoprint_version_string(), + 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(): @@ -645,7 +661,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 d75c7d24..476434e6 100644 --- a/src/octoprint/plugins/pluginmanager/static/js/pluginmanager.js +++ b/src/octoprint/plugins/pluginmanager/static/js/pluginmanager.js @@ -68,6 +68,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", { @@ -134,6 +140,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; @@ -148,11 +158,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() { @@ -162,7 +176,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() { @@ -172,7 +186,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({ @@ -229,7 +243,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) { @@ -250,6 +265,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; @@ -409,6 +435,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 ) +

+ + + + diff --git a/src/octoprint/plugins/softwareupdate/__init__.py b/src/octoprint/plugins/softwareupdate/__init__.py index 14f277a8..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, } @@ -416,14 +432,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, check.get("type", ""))) continue target_information = dict_merge(dict(local=dict(name="unknown", value="unknown"), remote=dict(name="unknown", value="unknown")), target_information) @@ -669,6 +684,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 +721,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 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 + } 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: diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 77ddcf78..ed303df8 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/server/util/flask.py b/src/octoprint/server/util/flask.py index cf561a29..4fb4d6cb 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 diff --git a/src/octoprint/static/js/app/viewmodels/settings.js b/src/octoprint/static/js/app/viewmodels/settings.js index f5c97ea2..d70d07c5 100644 --- a/src/octoprint/static/js/app/viewmodels/settings.js +++ b/src/octoprint/static/js/app/viewmodels/settings.js @@ -513,6 +513,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; + } } }; @@ -527,7 +550,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) { @@ -535,15 +561,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; } }); @@ -620,22 +640,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); } }); }; 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 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"]): diff --git a/src/octoprint/util/pip.py b/src/octoprint/util/pip.py index c805c21f..bc2c480d 100644 --- a/src/octoprint/util/pip.py +++ b/src/octoprint/util/pip.py @@ -27,14 +27,17 @@ class PipCaller(CommandlineCaller): CommandlineCaller.__init__(self) self._logger = logging.getLogger(__name__) - self._configured = configured + self.configured = configured + self.refresh = False 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 def __le__(self, other): return self.version is not None and self.version <= other @@ -60,10 +63,18 @@ class PipCaller(CommandlineCaller): 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() @@ -81,7 +92,7 @@ class PipCaller(CommandlineCaller): return self.call(command) def _find_pip(self): - pip_command = self._configured + pip_command = self.configured pip_version = None if pip_command is None: