Merge remote-tracking branch 'upstream/devel' into dev/folderSupport

Conflicts:
	src/octoprint/static/js/app/viewmodels/files.js
This commit is contained in:
Salandora 2015-09-30 11:10:59 +02:00
commit 38a0f383ae
11 changed files with 362 additions and 144 deletions

View file

@ -38,11 +38,25 @@ For information about how to go about contributions of any kind, please see the
Installation
------------
Installation instructions for installing from source for different operating systems can be found [on the wiki](https://github.com/foosel/OctoPrint/wiki#assorted-guides).
Installation instructions for installing from source for different operating
systems can be found [on the wiki](https://github.com/foosel/OctoPrint/wiki#assorted-guides).
If you want to run OctoPrint on a Raspberry Pi you might want to take a look at [OctoPi](https://github.com/guysoft/OctoPi)
which is a custom SD card image that includes OctoPrint plus dependencies.
The generic steps that should basically be done regardless of operating system
and runtime environment are the following (as *regular
user*, please keep your hands *off* of the `sudo` command here!) - this assumes
you already have Python 2.7, pip and virtualenv set up:
1. Checkout OctoPrint: `git clone https://github.com/foosel/OctoPrint.git`
2. Change into the OctoPrint folder: `cd OctoPrint`
3. Create a user-owned virtual environment therein: `virtualenv --system-site-packages venv`
4. Install OctoPrint *into that virtual environment*: `./venv/bin/python setup.py install`
You may then start the OctoPrint server via `/path/to/OctoPrint/venv/bin/octoprint`, see [Usage](#usage)
for details.
After installation, please make sure you follow the first-run wizard and set up
access control as necessary. If you want to not only be notified about new
releases but also be able to automatically upgrade to them from within

View file

@ -15,7 +15,7 @@ Log file management
Retrieve a list of available log files
======================================
.. http:post:: /api/logs
.. http:get:: /api/logs
Retrieve information regarding all log files currently available and regarding the disk space still available
in the system on the location the log files are being stored.

View file

@ -83,14 +83,23 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
repository="http://plugins.octoprint.org/plugins.json",
repository_ttl=24*60,
pip=None,
pip_args=None,
dependency_links=False,
hidden=[]
)
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 +187,19 @@ 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=self._pip_caller.version_string,
use_sudo=self._pip_caller.use_sudo,
additional_args=self._settings.get(["pip_args"])
))
def on_api_command(self, command, data):
if not admin_permission.can():
@ -437,6 +456,10 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
if "--process-dependency-links" in args:
self._log_message(u"Installation needs to process external dependencies, that might make it take a bit longer than usual depending on the pip version")
additional_args = self._settings.get(["pip_args"])
if additional_args:
args.append(additional_args)
return self._pip_caller.execute(*args)
def _log_message(self, *lines):
@ -645,7 +668,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

@ -6,6 +6,13 @@ $(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.config_pipAdditionalArgs = ko.observable();
self.configurationDialog = $("#settings_plugin_pluginmanager_configurationdialog");
self.plugins = new ItemListHelper(
"plugin.pluginmanager.installedplugins",
{
@ -72,6 +79,12 @@ $(function() {
self.followDependencyLinks = ko.observable(false);
self.pipAvailable = ko.observable(false);
self.pipCommand = ko.observable();
self.pipVersion = ko.observable();
self.pipUseSudo = ko.observable();
self.pipAdditionalArgs = ko.observable();
self.working = ko.observable(false);
self.workingTitle = ko.observable();
self.workingDialog = undefined;
@ -86,11 +99,15 @@ $(function() {
};
self.enableUninstall = function(data) {
return self.enableManagement() && !data.bundled && data.key != 'pluginmanager' && !data.pending_uninstall;
return self.enableManagement()
&& (data.origin != "entry_point" || self.pipAvailable())
&& !data.bundled
&& data.key != 'pluginmanager'
&& !data.pending_uninstall;
};
self.enableRepoInstall = function(data) {
return self.enableManagement() && self.isCompatible(data);
return self.enableManagement() && self.pipAvailable() && self.isCompatible(data);
};
self.invalidUrl = ko.computed(function() {
@ -100,7 +117,7 @@ $(function() {
self.enableUrlInstall = ko.computed(function() {
var url = self.installUrl();
return self.enableManagement() && url !== undefined && url.trim() != "" && !self.invalidUrl();
return self.enableManagement() && self.pipAvailable() && url !== undefined && url.trim() != "" && !self.invalidUrl();
});
self.invalidArchive = ko.computed(function() {
@ -110,7 +127,7 @@ $(function() {
self.enableArchiveInstall = ko.computed(function() {
var name = self.uploadFilename();
return self.enableManagement() && name !== undefined && name.trim() != "" && !self.invalidArchive();
return self.enableManagement() && self.pipAvailable() && name !== undefined && name.trim() != "" && !self.invalidArchive();
});
self.uploadElement.fileupload({
@ -167,7 +184,8 @@ $(function() {
self.fromResponse = function(data) {
self._fromPluginsResponse(data.plugins);
self._fromRepositoryResponse(data.repository)
self._fromRepositoryResponse(data.repository);
self._fromPipResponse(data.pip);
};
self._fromPluginsResponse = function(data) {
@ -188,6 +206,21 @@ $(function() {
}
};
self._fromPipResponse = function(data) {
self.pipAvailable(data.available);
if (data.available) {
self.pipCommand(data.command);
self.pipVersion(data.version);
self.pipUseSudo(data.use_sudo);
self.pipAdditionalArgs(data.additional_args);
} else {
self.pipCommand(undefined);
self.pipVersion(undefined);
self.pipUseSudo(undefined);
self.pipAdditionalArgs(undefined);
}
};
self.requestData = function(includeRepo) {
if (!self.loginState.isAdmin()) {
return;
@ -343,6 +376,58 @@ $(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 pipArgs = self.config_pipAdditionalArgs();
if (pipArgs != undefined && pipArgs.trim() == "") {
pipArgs = null;
}
var data = {
plugins: {
pluginmanager: {
repository: repository,
repository_ttl: repositoryTtl,
pip: pipCommand,
pip_args: pipArgs
}
}
};
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.config_pipAdditionalArgs(self.settingsViewModel.settings.plugins.pluginmanager.pip_args());
};
self.installed = function(data) {
return _.includes(self.installedPlugins(), data.id);
};

View file

@ -4,7 +4,31 @@
</div>
{% endmacro %}
{% macro pluginmanager_nopip() %}
<div class="alert" data-bind="visible: !pipAvailable()">{% trans %}
The <code>pip</code> command could not be found.
Please configure it manually. No installation and uninstallation of plugin
packages is possible while <code>pip</code> is unavailable.
{% endtrans %}</div>
{% endmacro %}
{% macro pluginmanager_sudopip() %}
<div class="alert alert-error" data-bind="visible: pipUseSudo()">{% trans %}
The <code>pip</code> command is configured to use <code>sudo</code>. This
is <strong>not</strong> recommended due to security reasons. It is <strong>strongly</strong>
suggested you install OctoPrint under a
<a href="https://github.com/foosel/OctoPrint/#installation">user-owned virtual environment</a>
so that the use of <code>sudo</code> is not needed for plugin management.
{% endtrans %}</div>
{% endmacro %}
{{ pluginmanager_printing() }}
{{ pluginmanager_nopip() }}
{{ pluginmanager_sudopip() }}
<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 +71,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><span data-bind="visible: pipAdditionalArgs">, additional arguments: <span data-bind="text: pipAdditionalArgs"></span></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 +95,8 @@
</div>
<div class="modal-body">
{{ pluginmanager_printing() }}
{{ pluginmanager_nopip() }}
{{ pluginmanager_sudopip() }}
<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 +197,47 @@
<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="{{ _('Additional arguments for pip command. You should normally not have to change this.') }}">
<label class="control-label">{{ _('Additional pip arguments') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: config_pipAdditionalArgs">
</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

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

File diff suppressed because one or more lines are too long

View file

@ -21,6 +21,15 @@ $(function() {
headers: {"X-Api-Key": UI_API_KEY}
});
//~~ Initialize file upload plugin
$.widget("blueimp.fileupload", $.blueimp.fileupload, {
options: {
dropZone: null,
pasteZone: null
}
});
//~~ Initialize i18n
var catalog = window["BABEL_TO_LOAD_" + LOCALE];

View file

@ -53,6 +53,7 @@ $(function() {
});
self.uploadButton = undefined;
self.uploadSdButton = undefined;
self.allItems = ko.observable(undefined);
self.listStyle = ko.observable("folders_files");
@ -452,10 +453,16 @@ $(function() {
self.onUserLoggedIn = function(user) {
self.uploadButton.fileupload("enable");
if (self.uploadSdButton) {
self.uploadSdButton.fileupload("enable");
}
};
self.onUserLoggedOut = function() {
self.uploadButton.fileupload("disable");
if (self.uploadSdButton) {
self.uploadSdButton.fileupload("disable");
}
};
self.onStartup = function() {
@ -482,6 +489,31 @@ $(function() {
//~~ Gcode upload
self.uploadButton = $("#gcode_upload");
self.uploadSdButton = $("#gcode_upload_sd");
if (!self.uploadSdButton.length) {
self.uploadSdButton = undefined;
}
var uploadProgress = $("#gcode_upload_progress");
var uploadProgressBar = uploadProgress.find(".bar");
var localTarget = CONFIG_SD_SUPPORT ? $("#drop_locally") : $("#drop");
var sdTarget = $("#drop_sd");
function setProgressBar(percentage, text, active) {
uploadProgressBar
.css("width", percentage + "%")
.text(text);
if (active) {
uploadProgress
.addClass("progress-striped active");
} else {
uploadProgress
.removeClass("progress-striped active");
}
}
function gcode_upload_done(e, data) {
var filename = undefined;
var location = undefined;
@ -499,9 +531,7 @@ $(function() {
}
if (data.result.done) {
$("#gcode_upload_progress .bar").css("width", "0%");
$("#gcode_upload_progress").removeClass("progress-striped").removeClass("active");
$("#gcode_upload_progress .bar").text("");
setProgressBar(0, "", false);
}
}
@ -514,113 +544,44 @@ $(function() {
type: "error",
hide: false
});
$("#gcode_upload_progress .bar").css("width", "0%");
$("#gcode_upload_progress").removeClass("progress-striped").removeClass("active");
$("#gcode_upload_progress .bar").text("");
setProgressBar(0, "", false);
}
function gcode_upload_progress(e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$("#gcode_upload_progress .bar").css("width", progress + "%");
$("#gcode_upload_progress .bar").text(gettext("Uploading ..."));
if (progress >= 100) {
$("#gcode_upload_progress").addClass("progress-striped").addClass("active");
$("#gcode_upload_progress .bar").text(gettext("Saving ..."));
}
var uploaded = progress >= 100;
setProgressBar(progress, uploaded ? gettext("Saving ...") : gettext("Uploading ..."), uploaded);
}
function enable_local_dropzone() {
$("#gcode_upload").fileupload({
url: API_BASEURL + "files/local",
dataType: "json",
dropZone: localTarget,
done: gcode_upload_done,
fail: gcode_upload_fail,
progressall: gcode_upload_progress
}).bind('fileuploadsubmit', function(e, data) {
data.formData = { path: self.currentPath() };
});
}
function setDropzone(dropzone, enable) {
var button = (dropzone == "local") ? self.uploadButton : self.uploadSdButton;
var drop = (dropzone == "local") ? localTarget : sdTarget;
var url = API_BASEURL + "files/" + dropzone;
function disable_local_dropzone() {
$("#gcode_upload").fileupload({
url: API_BASEURL + "files/local",
button.fileupload({
url: url,
dataType: "json",
dropZone: null,
dropZone: enable ? drop : null,
drop: function(e, data) {
},
done: gcode_upload_done,
fail: gcode_upload_fail,
progressall: gcode_upload_progress
});
}
function enable_sd_dropzone() {
$("#gcode_upload_sd").fileupload({
url: API_BASEURL + "files/sdcard",
dataType: "json",
dropZone: $("#drop_sd"),
done: gcode_upload_done,
fail: gcode_upload_fail,
progressall: gcode_upload_progress
}).bind('fileuploadsubmit', function(e, data) {
data.formData = { path: self.currentPath() };
});
}
function disable_sd_dropzone() {
$("#gcode_upload_sd").fileupload({
url: API_BASEURL + "files/sdcard",
dataType: "json",
dropZone: null,
done: gcode_upload_done,
fail: gcode_upload_fail,
progressall: gcode_upload_progress
});
}
var localTarget;
if (CONFIG_SD_SUPPORT) {
localTarget = $("#drop_locally");
} else {
localTarget = $("#drop");
}
self.loginState.isUser.subscribe(function(newValue) {
if (newValue === true) {
enable_local_dropzone();
} else {
disable_local_dropzone();
}
});
if (self.loginState.isUser()) {
enable_local_dropzone();
} else {
disable_local_dropzone();
}
if (CONFIG_SD_SUPPORT) {
self.printerState.isSdReady.subscribe(function(newValue) {
if (newValue === true && self.loginState.isUser()) {
enable_sd_dropzone();
} else {
disable_sd_dropzone();
}
});
self.loginState.isUser.subscribe(function(newValue) {
if (newValue === true && self.printerState.isSdReady()) {
enable_sd_dropzone();
} else {
disable_sd_dropzone();
}
});
if (self.printerState.isSdReady() && self.loginState.isUser()) {
enable_sd_dropzone();
} else {
disable_sd_dropzone();
}
function evaluateDropzones() {
var enableLocal = self.loginState.isUser();
var enableSd = enableLocal && CONFIG_SD_SUPPORT && self.printerState.isSdReady();
setDropzone("local", enableLocal);
setDropzone("sdcard", enableSd);
}
self.loginState.isUser.subscribe(evaluateDropzones);
self.printerState.isSdReady.subscribe(evaluateDropzones);
evaluateDropzones();
$(document).bind("dragover", function (e) {
var dropOverlay = $("#drop_overlay");
@ -633,7 +594,7 @@ $(function() {
var timeout = window.dropZoneTimeout;
if (!timeout) {
dropOverlay.addClass('in');
dropOverlay.addClass("in");
} else {
clearTimeout(timeout);
}

View file

@ -540,6 +540,29 @@ $(function() {
additionalBaudrates: function() { return _.map(splitTextToArray(self.serial_additionalBaudrates(), ",", true, function(item) { return !isNaN(parseInt(item)); }), function(item) { return parseInt(item); }) },
longRunningCommands: function() { return splitTextToArray(self.serial_longRunningCommands(), ",", true) },
checksumRequiringCommands: function() { return splitTextToArray(self.serial_checksumRequiringCommands(), ",", true) }
},
scripts: {
gcode: function() {
// we have a special handler function for the gcode scripts since the
// server will always send us those that have been set already, so we
// can't depend on all keys that we support to be present in the
// original request we iterate through in mapFromObservables to
// generate our response - hence we use our observables instead
//
// Note: If we ever introduce sub categories in the gcode scripts
// here (more _ after the prefix), we'll need to adjust this code
// to be able to cope with that, right now it only strips the prefix
// and uses the rest as key in the result, no recursive translation
// is done!
var result = {};
var prefix = "scripts_gcode_";
var observables = _.filter(_.keys(self), function(key) { return _.startsWith(key, prefix); });
_.each(observables, function(observable) {
var script = observable.substring(prefix.length);
result[script] = self[observable]();
});
return result;
}
}
};
@ -554,7 +577,10 @@ $(function() {
observable = keyPrefix + "_" + observable;
}
if (_.isPlainObject(value)) {
if (mapping && mapping[key] && _.isFunction(mapping[key])) {
result[key] = mapping[key]();
flag = true;
} else if (_.isPlainObject(value)) {
// value is another object, we'll dive deeper
var subresult = mapFromObservables(value, (mapping && mapping[key]) ? mapping[key] : undefined, observable);
if (subresult != undefined) {
@ -562,15 +588,9 @@ $(function() {
result[key] = subresult;
flag = true;
}
} else {
// if we have a custom read function for this, we'll use it
if (mapping && mapping[key] && _.isFunction(mapping[key])) {
result[key] = mapping[key]();
flag = true;
} else if (self.hasOwnProperty(observable)) {
result[key] = self[observable]();
flag = true;
}
} else if (self.hasOwnProperty(observable)) {
result[key] = self[observable]();
flag = true;
}
});
@ -647,22 +667,17 @@ $(function() {
observable = keyPrefix + "_" + observable;
}
if (_.isPlainObject(value)) {
var haveLocalVersion = local && local.hasOwnProperty(key);
if (mapping && mapping[key] && _.isFunction(mapping[key]) && !haveLocalVersion) {
// if we have a custom apply function for this, we'll use it
mapping[key](value);
} else if (_.isPlainObject(value)) {
// value is another object, we'll dive deeper
mapToObservables(value, (mapping && mapping[key]) ? mapping[key] : undefined, (local && local[key]) ? local[key] : undefined, observable);
} else {
// if we have a local version of this field, we'll not override it
if (local && local.hasOwnProperty(key)) {
return;
}
if (mapping && mapping[key] && _.isFunction(mapping[key])) {
// if we have a custom apply function for this, we'll use it
mapping[key](value);
} else if (self.hasOwnProperty(observable)) {
// else if we have a matching observable, we'll use that
self[observable](value);
}
} else if (!haveLocalVersion && self.hasOwnProperty(observable)) {
// if we have a matching observable, we'll use that
self[observable](value);
}
});
};

View file

@ -27,14 +27,19 @@ 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._version_string = None
self._use_sudo = False
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
@ -56,14 +61,30 @@ class PipCaller(CommandlineCaller):
def version(self):
return self._version
@property
def version_string(self):
return self._version_string
@property
def use_sudo(self):
return self._use_sudo
@property
def available(self):
return self._command is not None
def trigger_refresh(self):
try:
self._command, self._version, self._version_string, self._use_sudo = 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()
@ -77,12 +98,22 @@ class PipCaller(CommandlineCaller):
self._logger.debug("Version {} needs --no-use-wheel to properly work.".format(self.version))
arg_list.append("--no-use-wheel")
command = [self._command] + arg_list
command = [self._command] + list(arg_list)
if self._use_sudo:
command = ["sudo"] + command
return self.call(command)
def _find_pip(self):
pip_command = self._configured
pip_command = self.configured
if pip_command is not None and pip_command.startswith("sudo "):
pip_command = pip_command[len("sudo "):]
pip_sudo = True
else:
pip_sudo = False
pip_version = None
version_segment = None
if pip_command is None:
import os
@ -114,7 +145,11 @@ class PipCaller(CommandlineCaller):
if pip_command is not None:
self._logger.debug("Found pip at {}, going to figure out its version".format(pip_command))
p = sarge.run([pip_command, "--version"], stdout=sarge.Capture(), stderr=sarge.Capture())
sarge_command = [pip_command, "--version"]
if pip_sudo:
sarge_command = ["sudo"] + sarge_command
p = sarge.run(sarge_command, stdout=sarge.Capture(), stderr=sarge.Capture())
if p.returncode != 0:
self._logger.warn("Error while trying to run pip --version: {}".format(p.stderr.text))
@ -140,12 +175,12 @@ class PipCaller(CommandlineCaller):
pip_version = pkg_resources.parse_version(version_segment)
except:
self._logger.exception("Error while trying to parse version string from pip command")
return None, None
return None, None, None
else:
self._logger.info("Found pip at {}, version is {}".format(pip_command, version_segment))
if pip_version in self.__class__.broken:
self._logger.error("This version of pip is known to have errors that make it incompatible with how it needs to be used by OctoPrint. Please upgrade your pip version.")
return None, None
return None, None, None, False
return pip_command, pip_version
return pip_command, pip_version, version_segment, pip_sudo