@@ -11,9 +11,8 @@
from octoprint.server import admin_permission, NO_CONTENT
from octoprint.server.util.flask import restricted_access
from octoprint.util import is_hidden_path
from octoprint.util.version import get_octoprint_version_string, get_octoprint_version, get_comparable_version, is_octoprint_compatible
from octoprint.util.version import get_octoprint_version_string, get_octoprint_version, get_comparable_version
from octoprint.util.pip import LocalPipCaller
from octoprint.util.platform import is_os_compatible

try:
from os import scandir
@@ -46,12 +45,6 @@

UNKNOWN_PLUGINS_FILE = "unknown_plugins_from_restore.json"

"""
TODO:
* check for plugin compatibility (OS, OctoPrint version) on install during restore
* caching for GET endpoints on API
"""


class BackupPlugin(octoprint.plugin.SettingsPlugin,
octoprint.plugin.TemplatePlugin,
@@ -85,7 +78,6 @@ def get_assets(self):
@admin_permission.require(403)
@restricted_access
def get_state(self):
# TODO add caching
backups = self._get_backups()
unknown_plugins = self._get_unknown_plugins()
return flask.jsonify(backups=backups,
@@ -115,7 +107,6 @@ def delete_unknown_plugins(self):
@admin_permission.require(403)
@restricted_access
def get_backups(self):
# TODO add caching
backups = self._get_backups()
return flask.jsonify(backups=backups)

@@ -208,6 +199,24 @@ def on_log(line):
self._send_client_message("logline", dict(line=line, type="stdout"))

for plugin in plugins:
octoprint_compatible = plugin["compatibility"]["octoprint"]
os_compatible = plugin["compatibility"]["os"]
compatible = octoprint_compatible and os_compatible
if not compatible:
if not octoprint_compatible and not os_compatible:
self._logger.warn(u"Cannot install plugin {}, it is incompatible to this version "
u"of OctoPrint and the underlying operating system".format(plugin["id"]))
elif not octoprint_compatible:
self._logger.warn(u"Cannot install plugin {}, it is incompatible to this version "
u"of OctoPrint".format(plugin["id"]))
elif not os_compatible:
self._logger.warn(u"Cannot install plugin {}, it is incompatible to the underlying "
u"operating system".format(plugin["id"]))
self._send_client_message("plugin_incompatible", dict(plugin=plugin["id"],
octoprint_compatible=octoprint_compatible,
os_compatible=os_compatible))
continue

self._logger.info(u"Installing plugin {}".format(plugin["id"]))
self._send_client_message("installing_plugin", dict(plugin=plugin["id"]))
self.__class__._install_plugin(plugin,
@@ -350,6 +359,21 @@ def log(line):
click.echo(u"\t{}".format(line))

for plugin in plugins:
octoprint_compatible = plugin["compatibility"]["octoprint"]
os_compatible = plugin["compatibility"]["os"]
compatible = octoprint_compatible and os_compatible
if not compatible:
if not octoprint_compatible and not os_compatible:
click.echo(u"Cannot install plugin {}, it is incompatible to this version of "
u"OctoPrint and the underlying operating system".format(plugin["id"]))
elif not octoprint_compatible:
click.echo(u"Cannot install plugin {}, it is incompatible to this version of "
u"OctoPrint".format(plugin["id"]))
elif not os_compatible:
click.echo(u"Cannot install plugin {}, it is incompatible to the underlying "
u"operating system".format(plugin["id"]))
continue

click.echo(u"Installing plugin {}".format(plugin["id"]))
self.__class__._install_plugin(plugin,
force_user=force_user,
@@ -14,14 +14,6 @@
return this.base.get(this.url, opts);
};

OctoPrintBackupClient.prototype.getWithRefresh = function(opts) {
return this.get(true, opts);
};

OctoPrintBackupClient.prototype.getWithoutRefresh = function(opts) {
return this.get(false, opts);
};

OctoPrintBackupClient.prototype.createBackup = function(exclude, opts) {
exclude = exclude || [];

@@ -234,7 +226,16 @@ $(function() {
self.restoreInProgress(false);
} else if (data.type === "install_plugin") {
self.loglines.push({line: " ", stream: "message"});
self.loglines.push({line: _.sprintf(gettext("Installing plugin \"%(plugin)s}\"..."), {plugin: data.plugin.name}), stream: "message"});
self.loglines.push({
line: _.sprintf(gettext("Installing plugin \"%(plugin)s}\"..."), {plugin: data.plugin.name}),
stream: "message"
});
} else if (data.type === "plugin_incompatible") {
self.loglines.push({line: " ", stream: "message"});
self.loglines.push({
line: _.sprintf(gettext("Cannot install plugin \"%(plugin)s\" due to it being incompatible to this OctoPrint version and/or underlying operating system"), {plugin: data.plugin.key}),
stream: "stderr"
});
} else if (data.type === "unknown_plugins") {
if (data.plugins.length > 0) {
self.loglines.push({line: " ", stream: "message"});
@@ -875,30 +875,6 @@ def _refresh_notices(self, notice_data=None):
self._notices = notices
return True

@staticmethod
def _is_os_compatible(current_os, compatibility_entries):
"""
Tests if the ``current_os`` or ``sys.platform`` are blacklisted or whitelisted in ``compatibility_entries``
"""
if len(compatibility_entries) == 0:
# shortcut - no compatibility info means we are compatible
return True

negative_entries = map(lambda x: x[1:], filter(lambda x: x.startswith("!"), compatibility_entries))
positive_entries = filter(lambda x: not x.startswith("!"), compatibility_entries)

negative_match = False
if negative_entries:
# check if we are blacklisted
negative_match = current_os in negative_entries or any(map(lambda x: sys.platform.startswith(x), negative_entries))

positive_match = True
if positive_entries:
# check if we are whitelisted
positive_match = current_os in positive_entries or any(map(lambda x: sys.platform.startswith(x), positive_entries))

return positive_match and not negative_match

@property
def _reconnect_hooks(self):
reconnect_hooks = self.__class__.RECONNECT_HOOKS

This file was deleted.

@@ -28,3 +28,29 @@ def test_get_os(self, sys_platform, expected):
from octoprint.util.platform import get_os
actual = get_os()
self.assertEqual(actual, expected)

@ddt.data(
("linux", "linux2", [], True),
("linux", "linux2", ["linux", "freebsd"], True),
("windows", "win32", ["linux", "freebsd"], False),
("linux", "linux2", ["!windows"], True),
("windows", "win32", ["!windows"], False),
("unmapped", "os2", [], True),
("unmapped", "os2", ["linux", "freebsd"], False),
("unmapped", "os2", ["!os2"], False),
("unmapped", "sunos5", ["linux", "freebsd", "sunos"], True),
("unmapped", "sunos5", ["!sunos", "!os2"], False),
# both black and white listing at the same time usually doesn't
# make a whole lot of sense, but let's test it anyhow
("linux", "linux2", ["!windows", "linux", "freebsd"], True),
("linux", "linux2", ["!windows", "freebsd"], False),
("windows", "win32", ["!windows", "linux", "freebsd"], False),
("unmapped", "sunos5", ["!windows", "linux", "freebsd"], False)
)
@ddt.unpack
def test_is_os_compatible(self, current_os, sys_platform, entries, expected):
with mock.patch("sys.platform", sys_platform):
from octoprint.util.platform import is_os_compatible
actual = is_os_compatible(entries, current_os=current_os)
self.assertEqual(actual, expected)