From 27dcef3e2350c5fa96e63d127990d9ecc4f91e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 3 Jun 2015 10:46:58 +0200 Subject: [PATCH] New approach to matching up installed packages with plugins Match against entry_point origins using both package name and package name plus version. If one of those matches (depends on pip version if output will include version or not), it's the plugin we are looking for. This way we don't need to try to strip a version number from the package name as output by pip without knowing if a version number is included or not. --- .../plugins/pluginmanager/__init__.py | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/src/octoprint/plugins/pluginmanager/__init__.py b/src/octoprint/plugins/pluginmanager/__init__.py index 9aebcc08..55ce656f 100644 --- a/src/octoprint/plugins/pluginmanager/__init__.py +++ b/src/octoprint/plugins/pluginmanager/__init__.py @@ -174,7 +174,8 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, all_plugins_before = self._plugin_manager.find_plugins() - success_string = "Successfully installed " + success_string = "Successfully installed" + failure_string = "Could not install" try: returncode, stdout, stderr = self._call_pip(pip_args) except: @@ -190,39 +191,49 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, return make_response("Could not install plugin from url, see the log for more details", 500) try: - last_line = filter(lambda x: x.startswith(success_string) or x.startswith("Could not install"), stdout)[-1] + result_line = filter(lambda x: x.startswith(success_string) or x.startswith(failure_string), stdout)[-1] except IndexError: result = dict(result=False, reason="Could not parse output from pip") self._send_result_notification("install", result) return jsonify(result) - # The success line of a pip install command looks something like this: + # The final output of a pip install command looks something like this: # # Successfully installed OctoPrint-Plugin-1.0 Dependency-One-0.1 Dependency-Two-9.3 # - # So we'll first strip the first part, then split by whitespace and remove the version to get the name of - # the installed package. By then matching up the package names stored for all our entry point plugins against - # the list of installed packages from our pip install run, we'll find the plugin that was installed. + # or this: + # + # Successfully installed OctoPrint-Plugin Dependency-One Dependency-Two + # Cleaning up... + # + # So we'll need to fetch the "Successfully installed" line, strip the "Successfully" part, then split by whitespace + # and strip to get all installed packages. + # + # We then need to iterate over all known plugins and see if either the package name or the package name plus + # version number matches one of our installed packages. If it does, that's our installed plugin. + # + # Known issue: This might return the wrong plugin if more than one plugin was installed through this + # command (e.g. due to pulling in another plugin as dependency). It should be safe for now though to + # consider this a rare corner case. Once it becomes a real problem we'll just extend the plugin manager + # so that it can report on more than one installed plugin. - last_line = last_line.strip() - if not last_line.startswith(success_string): + result_line = result_line.strip() + if not result_line.startswith(success_string): result = dict(result=False, reason="Pip did not report successful installation") self._send_result_notification("install", result) return jsonify(result) - def strip_version(s): - l = s.strip().split("-") - if len(l) > 0 and s[-1][:1].isdigit(): - del l[-1] - return "-".join(l) - - installed = map(strip_version, last_line[len(success_string):].split(" ")) + installed = map(lambda x: x.strip(), result_line[len(success_string):].split(" ")) all_plugins_after = self._plugin_manager.find_plugins(existing=dict(), ignore_uninstalled=False) + for key, plugin in all_plugins_after.items(): - if plugin.origin is None or plugin.origin[0] != "entry_point": + if plugin.origin is None or plugin.origin.type != "entry_point": continue - if plugin.origin[3] in installed: + package_name = plugin.origin.package_name + package_version = plugin.origin.package_version + versioned_package = "{package_name}-{package_version}".format(**locals()) + if package_name in installed or versioned_package in installed: new_plugin_key = key new_plugin = plugin break @@ -248,7 +259,11 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, if plugin.bundled: return make_response("Bundled plugins cannot be uninstalled", 400) - if plugin.origin[0] == "entry_point": + if plugin.origin is None: + self._logger.warn(u"Trying to uninstall plugin {plugin} but origin is unknown".format(**locals())) + return make_response("Could not uninstall plugin, its origin is unknown") + + if plugin.origin.type == "entry_point": # plugin is installed through entry point, need to use pip to uninstall it origin = plugin.origin[3] if origin is None: @@ -261,7 +276,7 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, self._logger.exception(u"Could not uninstall plugin via pip") return make_response("Could not uninstall plugin via pip, see the log for more details", 500) - elif plugin.origin[0] == "folder": + elif plugin.origin.type == "folder": import os import shutil full_path = os.path.realpath(plugin.location) @@ -279,6 +294,10 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, if os.path.isfile(pyc_file): os.remove(pyc_file) + else: + self._logger.warn(u"Trying to uninstall plugin {plugin} but origin is unknown ({plugin.origin.type})".format(**locals())) + return make_response("Could not uninstall plugin, its origin is unknown") + needs_restart = self._plugin_manager.is_restart_needing_plugin(plugin) needs_refresh = plugin.implementation and isinstance(plugin.implementation, octoprint.plugin.ReloadNeedingPlugin)