Merge branch 'devel' into dev/clientlib

This commit is contained in:
Gina Häußge 2015-09-25 13:53:51 +02:00
commit e5f12ae4c0
18 changed files with 340 additions and 121 deletions

View file

@ -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

View file

@ -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...

2
run
View file

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2
import os
import sys

View file

@ -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

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
#!/usr/bin/env python2
import sys
from octoprint.daemon import Daemon
from octoprint.server import Server

View file

@ -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 <http://flask.pocoo.org/docs/0.10/api/#flask.Blueprint.errorhandler>`_
and `the documentation for flask.Flask.errorhandler <http://flask.pocoo.org/docs/0.10/api/#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):

View file

@ -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"

View file

@ -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);
};

View file

@ -4,7 +4,20 @@
</div>
{% endmacro %}
{% macro pluginmanager_nopip() %}
<div class="alert" data-bind="visible: !pipAvailable()">{% trans %}
The <code>pip</code> command could not be detected automatically,
please configure it manually. No installation and uninstallation of plugin
packages is possible while <code>pip</code> is unavailable.
{% endtrans %}</div>
{% endmacro %}
{{ pluginmanager_printing() }}
{{ pluginmanager_nopip() }}
<div class="pull-right">
<button class="btn btn-small" data-bind="click: function() { $root.showPluginSettings(); }" title="{{ _('Plugin Configuration') }}"><i class="icon-wrench"></i></button>
</div>
<h3>{{ _('Installed Plugins') }}</h3>
@ -47,6 +60,10 @@
<button class="btn btn-block" data-bind="click: $root.showRepository">{{ _('Get More...') }}</button>
<p class="muted" data-bind="visible: pipAvailable()">
<small>Using pip at "<span data-bind="text: pipCommand"></span>" (Version <span data-bind="text: pipVersion"></span>)</small>
</p>
<div id="settings_plugin_pluginmanager_workingdialog" class="modal hide fade">
<div class="modal-header">
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">&times;</a>
@ -67,6 +84,7 @@
</div>
<div class="modal-body">
{{ pluginmanager_printing() }}
{{ pluginmanager_nopip() }}
<h4 style="position: relative">
{{ _('... from the <a href="%(url)s" target="_blank">Plugin Repository</a>', url='http://plugins.octoprint.org') }}
<a class="dropdown-toggle pull-right" data-toggle="dropdown" href="#">
@ -167,3 +185,41 @@
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Close') }}</button>
</div>
</div>
<div id="settings_plugin_pluginmanager_configurationdialog" class="modal hide fade">
<div class="modal-header">
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">&times;</a>
<h3>{{ _('Plugin Configuration') }}</h3>
</div>
<div class="modal-body">
<form class="form-horizontal">
<div class="control-group" title="{{ _('pip command to use for managing plugins. You might have to configure this if auto detection fails.') }}">
<label class="control-label">{{ _('pip command') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: config_pipCommand" placeholder="{{ _('Autodetect') }}">
<span class="help-inline">{{ _('<strong>Only</strong> set this if OctoPrint cannot autodetect the path to <code>pip</code> to use for managing plugins.') }}</span>
</div>
</div>
<div class="control-group" title="{{ _('URL of the Plugin Repository to use. You should normally not have to change this.') }}">
<label class="control-label">{{ _('Repository URL') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: config_repositoryUrl">
</div>
</div>
<div class="control-group" title="{{ _('How long to cache repository data, in minutes. You should normally not have to change this.') }}">
<label class="control-label">{{ _('Repository cache TTL') }}</label>
<div class="controls">
<div class="input-append">
<input type="number" class="input-mini" data-bind="value: config_repositoryTtl">
<span class="add-on">min</span>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Cancel') }}</button>
<button class="btn btn-primary" data-bind="click: savePluginSettings" aria-hidden="true">{{ _('Save') }}</button>
</div>
</div>

View file

@ -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", "<n/a>")))
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

View file

@ -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
}

View file

@ -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:

View file

@ -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()

View file

@ -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

View file

@ -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);
}
});
};

View file

@ -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

View file

@ -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"]):

View file

@ -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: