Merge branch 'devel' into dev/clientlib
This commit is contained in:
commit
e5f12ae4c0
18 changed files with 340 additions and 121 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
2
run
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python2
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
|
|
|||
8
setup.py
8
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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python2
|
||||
|
||||
import sys
|
||||
from octoprint.daemon import Daemon
|
||||
from octoprint.server import Server
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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">×</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">×</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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"]):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue