コード例 #1
0
def run_system_command(command, password=None):
    logger = logging.getLogger(
        "octoprint.plugins.ws281x_led_status.commandline")
    caller = CommandlineCaller()
    try:
        if password:
            returncode, stdout, stderr = caller.call(command, input=password)
        else:
            returncode, stdout, stderr = caller.call(command)

    except Exception as e:
        logger.error("Error running command `{}`".format("".join(command)))
        logger.exception(e)
        return None, "exception"

    if returncode != 0:
        logger.error("Command for `{}` failed with return code {}".format(
            "".join(command), returncode))
        logger.error("STDOUT: {}".format(stdout))
        logger.error("STDOUT: {}".format(stderr))
        error = "command"
    else:
        # Convert output to joined string instead of list
        stdout = "\n".join(stdout)
        stderr = "\n".join(stderr)

        if stderr and "Sorry" in stderr or "no password" in stdout:
            error = "password"
        else:
            error = None

    return stdout, error
コード例 #2
0
ファイル: timelapse.py プロジェクト: tinkercnc/OctoPrint
    def _try_generate_thumbnail(cls, ffmpeg, movie_path):
        logger = logging.getLogger(__name__)

        try:
            thumb_path = create_thumbnail_path(movie_path)
            commandline = settings().get(
                ["webcam", "ffmpegThumbnailCommandline"])
            thumb_command_str = cls._create_ffmpeg_command_string(
                commandline=commandline,
                ffmpeg=ffmpeg,
                input=movie_path,
                output=thumb_path,
                fps=None,
                videocodec=None,
                threads=None,
                bitrate=None,
            )
            c = CommandlineCaller()
            returncode, stdout_text, stderr_text = c.call(thumb_command_str,
                                                          delimiter=b"\r",
                                                          buffer_size=512)
            if returncode != 0:
                logger.warning("Failed to generate optional thumbnail %r: %s" %
                               (returncode, stderr_text))
            return True
        except Exception as ex:
            logger.warning(
                "Failed to generate thumbnail from {} to {} ({})".format(
                    movie_path, thumb_path, ex))
            return False
コード例 #3
0
ファイル: dev.py プロジェクト: zarecor60/OctoPrint
	def __init__(self, *args, **kwargs):
		click.MultiCommand.__init__(self, *args, **kwargs)

		from octoprint.util.commandline import CommandlineCaller
		from functools import partial

		def log_util(f):
			def log(*lines):
					for line in lines:
						f(line)
			return log

		self.command_caller = CommandlineCaller()
		self.command_caller.on_log_call = log_util(lambda x: click.echo(">> {}".format(x)))
		self.command_caller.on_log_stdout = log_util(click.echo)
		self.command_caller.on_log_stderr = log_util(partial(click.echo, err=True))
コード例 #4
0
    def restart_octoprint(self):
        command = self._octoprint_settings.get(["server", "commands", "serverRestartCommand"])
        if not command:
            self._logger.warning("No command configured, can't restart")
            return

        caller = CommandlineCaller()
        try:
            code, stdout, stderr = caller.call(command, **{"shell": True}) # Use shell=True, as we have to trust user input
        except Exception as e:
            self._logger.error("Error calling command to restart server {}".format(command))
            self._logger.exception(e)
            return

        if code != 0:
            self._logger.error("Non zero return code running '{}' to restart server: {}".format(command, code))
            self._logger.exception("STDOUT: {}".format(stdout))
            self._logger.exception("STDERR: {}".format(stderr))
コード例 #5
0
def _get_caller(log_cb=None):
    def _log_call(*lines):
        _log(lines, prefix=" ", stream="call")

    def _log_stdout(*lines):
        _log(lines, prefix=">", stream="stdout")

    def _log_stderr(*lines):
        _log(lines, prefix="!", stream="stderr")

    def _log(lines, prefix=None, stream=None):
        if log_cb is None:
            return
        log_cb(lines, prefix=prefix, stream=stream)

    caller = CommandlineCaller()
    if log_cb is not None:
        caller.on_log_call = _log_call
        caller.on_log_stdout = _log_stdout
        caller.on_log_stderr = _log_stderr
    return caller
コード例 #6
0
ファイル: update_script.py プロジェクト: BigRep/OctoPrint
def _get_caller(log_cb=None):
	def _log_call(*lines):
		_log(lines, prefix=" ", stream="call")

	def _log_stdout(*lines):
		_log(lines, prefix=">", stream="stdout")

	def _log_stderr(*lines):
		_log(lines, prefix="!", stream="stderr")

	def _log(lines, prefix=None, stream=None):
		if log_cb is None:
			return
		log_cb(lines, prefix=prefix, stream=stream)

	caller = CommandlineCaller()
	if log_cb is not None:
		caller.on_log_call = _log_call
		caller.on_log_stdout = _log_stdout
		caller.on_log_stderr = _log_stderr
	return caller
コード例 #7
0
        def execute():
            # we run this with shell=True since we have to trust whatever
            # our admin configured as command and since we want to allow
            # shell-alike handling here...
            return_code, stdout_lines, stderr_lines = CommandlineCaller().call(
                command_spec["command"], shell=True)

            if not do_ignore and return_code != 0:
                stdout = "\n".join(stdout_lines)
                stderr = "\n".join(stderr_lines)
                error = f"Command for {source}:{command} failed with return code {return_code}:\n\nSTDOUT:\n{stdout}\n\nSTDERR:\n{stderr}"
                logger.warning(prefix_multilines(error, prefix="! "))
                if not do_async:
                    raise CommandFailed(error)
コード例 #8
0
ファイル: dev.py プロジェクト: BigRep/OctoPrint
	def __init__(self, *args, **kwargs):
		click.MultiCommand.__init__(self, *args, **kwargs)

		from octoprint.util.commandline import CommandlineCaller
		from functools import partial

		def log_util(f):
			def log(*lines):
					for line in lines:
						f(line)
			return log

		self.command_caller = CommandlineCaller()
		self.command_caller.on_log_call = log_util(lambda x: click.echo(">> {}".format(x)))
		self.command_caller.on_log_stdout = log_util(click.echo)
		self.command_caller.on_log_stderr = log_util(partial(click.echo, err=True))
コード例 #9
0
    def _do_copy_log(self):

        # Get the name of the tarball we're going to copy the log
        # files to.
        tarball_filename = self.build_tarball_filename()
        self._log("tarball = <%s>" % tarball_filename)

        # Get the location where we're going to save the tarball.
        logpath = self.find_logpath()
        self._log("logpath = <%s>" % logpath)

        # Build the full path to the tarball
        tarball_path = os.path.join(logpath, tarball_filename)
        self._log("tarball_path = <%s>" % tarball_path)

        # Get the names of the log files
        logfiles = self.get_log_file_names()
        self._log("logfiles = <%s>" % logfiles)

        # Now build the command.
        command = "tar cvf %s %s" % (tarball_path, logfiles)
        self._log("command = <%s>" % command)

        # Construct a commandline caller to run the tar command.
        caller = CommandlineCaller()
        caller.on_log_call = self.display_call_stdout
        caller.on_log_stdout = self.display_call_stdout
        caller.on_log_stderr = self.display_stderr

        # Execute the command.
        self.display("")
        caller.call(command)

        # Finish off with a message stating that the tarball has been
        # created.
        done_message = "Logs copied to file \"%s\"" % tarball_path
        self._log(done_message)
        self.display("\n" + done_message)
        self.display("")
コード例 #10
0
ファイル: dev.py プロジェクト: BigRep/OctoPrint
class OctoPrintDevelCommands(click.MultiCommand):
	"""
	Custom `click.MultiCommand <http://click.pocoo.org/5/api/#click.MultiCommand>`_
	implementation that provides commands relevant for (plugin) development
	based on availability of development dependencies.
	"""

	sep = ":"
	groups = ("plugin",)

	def __init__(self, *args, **kwargs):
		click.MultiCommand.__init__(self, *args, **kwargs)

		from octoprint.util.commandline import CommandlineCaller
		from functools import partial

		def log_util(f):
			def log(*lines):
					for line in lines:
						f(line)
			return log

		self.command_caller = CommandlineCaller()
		self.command_caller.on_log_call = log_util(lambda x: click.echo(">> {}".format(x)))
		self.command_caller.on_log_stdout = log_util(click.echo)
		self.command_caller.on_log_stderr = log_util(partial(click.echo, err=True))

	def _get_prefix_methods(self, method_prefix):
		for name in [x for x in dir(self) if x.startswith(method_prefix)]:
			method = getattr(self, name)
			yield method

	def _get_commands_from_prefix_methods(self, method_prefix):
		for method in self._get_prefix_methods(method_prefix):
			result = method()
			if result is not None and isinstance(result, click.Command):
				yield result

	def _get_commands(self):
		result = dict()
		for group in self.groups:
			for command in self._get_commands_from_prefix_methods("{}_".format(group)):
				result[group + self.sep + command.name] = command
		return result

	def list_commands(self, ctx):
		result = [name for name in self._get_commands()]
		result.sort()
		return result

	def get_command(self, ctx, cmd_name):
		commands = self._get_commands()
		return commands.get(cmd_name, None)

	def plugin_new(self):
		try:
			import cookiecutter.main
		except ImportError:
			return None

		try:
			# we depend on Cookiecutter >= 1.4
			from cookiecutter.prompt import StrictEnvironment
		except ImportError:
			return None

		import contextlib

		@contextlib.contextmanager
		def custom_cookiecutter_config(config):
			"""
			Allows overriding cookiecutter's user config with a custom dict
			with fallback to the original data.
			"""
			from octoprint.util import fallback_dict

			original_get_user_config = cookiecutter.main.get_user_config
			try:
				def f(*args, **kwargs):
					original_config = original_get_user_config(*args, **kwargs)
					return fallback_dict(config, original_config)
				cookiecutter.main.get_user_config = f
				yield
			finally:
				cookiecutter.main.get_user_config = original_get_user_config

		@contextlib.contextmanager
		def custom_cookiecutter_prompt(options):
			"""
			Custom cookiecutter prompter for the template config.

			If a setting is available in the provided options (read from the CLI)
			that will be used, otherwise the user will be prompted for a value
			via click.
			"""
			original_prompt_for_config = cookiecutter.main.prompt_for_config

			def custom_prompt_for_config(context, no_input=False):
				cookiecutter_dict = dict()

				env = StrictEnvironment()

				for key, raw in context['cookiecutter'].items():
					if key in options:
						val = options[key]
					else:
						raw = raw if isinstance(raw, basestring) else str(raw)
						val = env.from_string(raw).render(cookiecutter=cookiecutter_dict)

						if not no_input:
							val = click.prompt(key, default=val)

					cookiecutter_dict[key] = val
				return cookiecutter_dict

			try:
				cookiecutter.main.prompt_for_config = custom_prompt_for_config
				yield
			finally:
				cookiecutter.main.prompt_for_config = original_prompt_for_config

		@click.command("new")
		@click.option("--name", "-n", help="The name of the plugin")
		@click.option("--package", "-p", help="The plugin package")
		@click.option("--author", "-a", help="The plugin author's name")
		@click.option("--email", "-e", help="The plugin author's mail address")
		@click.option("--license", "-l", help="The plugin's license")
		@click.option("--description", "-d", help="The plugin's description")
		@click.option("--homepage", help="The plugin's homepage URL")
		@click.option("--source", "-s", help="The URL to the plugin's source")
		@click.option("--installurl", "-i", help="The plugin's install URL")
		@click.argument("identifier", required=False)
		def command(name, package, author, email, description, license, homepage, source, installurl, identifier):
			"""Creates a new plugin based on the OctoPrint Plugin cookiecutter template."""
			from octoprint.util import tempdir

			# deleting a git checkout folder might run into access errors due
			# to write-protected sub folders, so we use a custom onerror handler
			# that tries to fix such permissions
			def onerror(func, path, exc_info):
				"""Originally from http://stackoverflow.com/a/2656405/2028598"""
				import stat
				import os

				if not os.access(path, os.W_OK):
					os.chmod(path, stat.S_IWUSR)
					func(path)
				else:
					raise

			with tempdir(onerror=onerror) as path:
				custom = dict(cookiecutters_dir=path)
				with custom_cookiecutter_config(custom):
					raw_options = dict(
						plugin_identifier=identifier,
						plugin_package=package,
						plugin_name=name,
						full_name=author,
						email=email,
						plugin_description=description,
						plugin_license=license,
						plugin_homepage=homepage,
						plugin_source=source,
						plugin_installurl=installurl
					)
					options = dict((k, v) for k, v in raw_options.items() if v is not None)

					with custom_cookiecutter_prompt(options):
						cookiecutter.main.cookiecutter("gh:OctoPrint/cookiecutter-octoprint-plugin")

		return command

	def plugin_install(self):
		@click.command("install")
		@click.option("--path", help="Path of the local plugin development folder to install")
		def command(path):
			"""
			Installs the local plugin in development mode.

			Note: This can NOT be used to install plugins from remote locations
			such as the plugin repository! It is strictly for local development
			of plugins, to ensure the plugin is installed (editable) into the
			same python environment that OctoPrint is installed under.
			"""

			import os
			import sys

			if not path:
				path = os.getcwd()

			# check if this really looks like a plugin
			if not os.path.isfile(os.path.join(path, "setup.py")):
				click.echo("This doesn't look like an OctoPrint plugin folder")
				sys.exit(1)

			self.command_caller.call([sys.executable, "setup.py", "develop"], cwd=path)

		return command

	def plugin_uninstall(self):
		@click.command("uninstall")
		@click.argument("name")
		def command(name):
			"""Uninstalls the plugin with the given name."""
			import sys

			lower_name = name.lower()
			if not lower_name.startswith("octoprint_") and not lower_name.startswith("octoprint-"):
				click.echo("This doesn't look like an OctoPrint plugin name")
				sys.exit(1)

			call = [sys.executable, "-m", "pip", "uninstall", "--yes", name]
			self.command_caller.call(call)

		return command
コード例 #11
0
ファイル: dev.py プロジェクト: tinkercnc/OctoPrint
class OctoPrintDevelCommands(click.MultiCommand):
    """
    Custom `click.MultiCommand <http://click.pocoo.org/5/api/#click.MultiCommand>`_
    implementation that provides commands relevant for (plugin) development
    based on availability of development dependencies.
    """

    sep = ":"
    groups = ("plugin", "css")

    def __init__(self, *args, **kwargs):
        click.MultiCommand.__init__(self, *args, **kwargs)

        from functools import partial

        from octoprint.util.commandline import CommandlineCaller

        def log_util(f):
            def log(*lines):
                for line in lines:
                    f(line)

            return log

        self.command_caller = CommandlineCaller()
        self.command_caller.on_log_call = log_util(lambda x: click.echo(f">> {x}"))
        self.command_caller.on_log_stdout = log_util(click.echo)
        self.command_caller.on_log_stderr = log_util(partial(click.echo, err=True))

    def _get_prefix_methods(self, method_prefix):
        for name in [x for x in dir(self) if x.startswith(method_prefix)]:
            method = getattr(self, name)
            yield method

    def _get_commands_from_prefix_methods(self, method_prefix):
        for method in self._get_prefix_methods(method_prefix):
            result = method()
            if result is not None and isinstance(result, click.Command):
                yield result

    def _get_commands(self):
        result = {}
        for group in self.groups:
            for command in self._get_commands_from_prefix_methods(f"{group}_"):
                result[group + self.sep + command.name] = command
        return result

    def list_commands(self, ctx):
        result = [name for name in self._get_commands()]
        result.sort()
        return result

    def get_command(self, ctx, cmd_name):
        commands = self._get_commands()
        return commands.get(cmd_name, None)

    def plugin_new(self):
        try:
            import cookiecutter.main
        except ImportError:
            return None

        try:
            # we depend on Cookiecutter >= 1.4
            from cookiecutter.prompt import StrictEnvironment
        except ImportError:
            return None

        import contextlib

        @contextlib.contextmanager
        def custom_cookiecutter_config(config):
            """
            Allows overriding cookiecutter's user config with a custom dict
            with fallback to the original data.
            """
            from octoprint.util import fallback_dict

            original_get_user_config = cookiecutter.main.get_user_config
            try:

                def f(*args, **kwargs):
                    original_config = original_get_user_config(*args, **kwargs)
                    return fallback_dict(config, original_config)

                cookiecutter.main.get_user_config = f
                yield
            finally:
                cookiecutter.main.get_user_config = original_get_user_config

        @contextlib.contextmanager
        def custom_cookiecutter_prompt(options):
            """
            Custom cookiecutter prompter for the template config.

            If a setting is available in the provided options (read from the CLI)
            that will be used, otherwise the user will be prompted for a value
            via click.
            """
            original_prompt_for_config = cookiecutter.main.prompt_for_config

            def custom_prompt_for_config(context, no_input=False):
                cookiecutter_dict = {}

                env = StrictEnvironment()

                for key, raw in context["cookiecutter"].items():
                    if key in options:
                        val = options[key]
                    else:
                        if not isinstance(raw, str):
                            raw = str(raw)
                        val = env.from_string(raw).render(cookiecutter=cookiecutter_dict)

                        if not no_input:
                            val = click.prompt(key, default=val)

                    cookiecutter_dict[key] = val
                return cookiecutter_dict

            try:
                cookiecutter.main.prompt_for_config = custom_prompt_for_config
                yield
            finally:
                cookiecutter.main.prompt_for_config = original_prompt_for_config

        @click.command("new")
        @click.option("--name", "-n", help="The name of the plugin")
        @click.option("--package", "-p", help="The plugin package")
        @click.option("--author", "-a", help="The plugin author's name")
        @click.option("--email", "-e", help="The plugin author's mail address")
        @click.option("--license", "-l", help="The plugin's license")
        @click.option("--description", "-d", help="The plugin's description")
        @click.option("--homepage", help="The plugin's homepage URL")
        @click.option("--source", "-s", help="The URL to the plugin's source")
        @click.option("--installurl", "-i", help="The plugin's install URL")
        @click.argument("identifier", required=False)
        def command(
            name,
            package,
            author,
            email,
            description,
            license,
            homepage,
            source,
            installurl,
            identifier,
        ):
            """Creates a new plugin based on the OctoPrint Plugin cookiecutter template."""
            from octoprint.util import tempdir

            # deleting a git checkout folder might run into access errors due
            # to write-protected sub folders, so we use a custom onerror handler
            # that tries to fix such permissions
            def onerror(func, path, exc_info):
                """Originally from http://stackoverflow.com/a/2656405/2028598"""
                import os
                import stat

                if not os.access(path, os.W_OK):
                    os.chmod(path, stat.S_IWUSR)
                    func(path)
                else:
                    raise

            with tempdir(onerror=onerror) as path:
                custom = {"cookiecutters_dir": path}
                with custom_cookiecutter_config(custom):
                    raw_options = {
                        "plugin_identifier": identifier,
                        "plugin_package": package,
                        "plugin_name": name,
                        "full_name": author,
                        "email": email,
                        "plugin_description": description,
                        "plugin_license": license,
                        "plugin_homepage": homepage,
                        "plugin_source": source,
                        "plugin_installurl": installurl,
                    }
                    options = {k: v for k, v in raw_options.items() if v is not None}

                    with custom_cookiecutter_prompt(options):
                        cookiecutter.main.cookiecutter(
                            "gh:OctoPrint/cookiecutter-octoprint-plugin"
                        )

        return command

    def plugin_install(self):
        @click.command("install")
        @click.option(
            "--path", help="Path of the local plugin development folder to install"
        )
        def command(path):
            """
            Installs the local plugin in development mode.

            Note: This can NOT be used to install plugins from remote locations
            such as the plugin repository! It is strictly for local development
            of plugins, to ensure the plugin is installed (editable) into the
            same python environment that OctoPrint is installed under.
            """

            import os

            if not path:
                path = os.getcwd()

            # check if this really looks like a plugin
            if not os.path.isfile(os.path.join(path, "setup.py")):
                click.echo("This doesn't look like an OctoPrint plugin folder")
                sys.exit(1)

            self.command_caller.call(
                [sys.executable, "-m", "pip", "install", "-e", "."], cwd=path
            )

        return command

    def plugin_uninstall(self):
        @click.command("uninstall")
        @click.argument("name")
        def command(name):
            """Uninstalls the plugin with the given name."""

            lower_name = name.lower()
            if not lower_name.startswith("octoprint_") and not lower_name.startswith(
                "octoprint-"
            ):
                click.echo("This doesn't look like an OctoPrint plugin name")
                sys.exit(1)

            call = [sys.executable, "-m", "pip", "uninstall", "--yes", name]
            self.command_caller.call(call)

        return command

    def css_build(self):
        @click.command("build")
        @click.option(
            "--file",
            "-f",
            "files",
            multiple=True,
            help="Specify files to build, for a list of options use --list",
        )
        @click.option("--all", "all_files", is_flag=True, help="Build all less files")
        @click.option(
            "--list", "list_files", is_flag=True, help="List all available files and exit"
        )
        def command(files, all_files, list_files):
            import os.path
            import shutil

            available_files = {
                "core": {
                    "source": "static/less/octoprint.less",
                    "output": "static/css/octoprint.css",
                },
                "login": {
                    "source": "static/less/login.less",
                    "output": "static/css/login.css",
                },
                "recovery": {
                    "source": "static/less/recovery.less",
                    "output": "static/css/recovery.css",
                },
                "plugin_announcements": {
                    "source": "plugins/announcements/static/less/announcements.less",
                    "output": "plugins/announcements/static/css/announcements.css",
                },
                "plugin_appkeys_core": {
                    "source": "plugins/appkeys/static/less/appkeys.less",
                    "output": "plugins/appkeys/static/css/appkeys.css",
                },
                "plugin_appkeys_authdialog": {
                    "source": "plugins/appkeys/static/less/authdialog.less",
                    "output": "plugins/appkeys/static/css/authdialog.css",
                },
                "plugin_backup": {
                    "source": "plugins/backup/static/less/backup.less",
                    "output": "plugins/backup/static/css/backup.css",
                },
                "plugin_gcodeviewer": {
                    "source": "plugins/gcodeviewer/static/less/gcodeviewer.less",
                    "output": "plugins/gcodeviewer/static/css/gcodeviewer.css",
                },
                "plugin_logging": {
                    "source": "plugins/logging/static/less/logging.less",
                    "output": "plugins/logging/static/css/logging.css",
                },
                "plugin_pluginmanager": {
                    "source": "plugins/pluginmanager/static/less/pluginmanager.less",
                    "output": "plugins/pluginmanager/static/css/pluginmanager.css",
                },
                "plugin_softwareupdate": {
                    "source": "plugins/softwareupdate/static/less/softwareupdate.less",
                    "output": "plugins/softwareupdate/static/css/softwareupdate.css",
                },
            }

            if list_files:
                click.echo("Available files to build:")
                for name in available_files.keys():
                    click.echo(f"- {name}")
                sys.exit(0)

            if all_files:
                files = available_files.keys()

            if not files:
                click.echo(
                    "No files specified. Use `--file <file>` to specify individual files, or `--all` to build all."
                )
                sys.exit(1)

            # Check that lessc is installed
            less = shutil.which("lessc")

            if not less:
                click.echo(
                    "lessc is not installed/not available, please install it first"
                )
                click.echo(
                    "Try `npm i -g less` to install it (NOT lessc in this command!)"
                )
                sys.exit(1)

            # Find the folder of the `octoprint` package
            # Two folders up from this file
            octoprint = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

            for file in files:
                if file not in available_files.keys():
                    click.echo(f"Unknown file {file}")
                    sys.exit(1)

                source_path = os.path.join(octoprint, available_files[file]["source"])
                output_path = os.path.join(octoprint, available_files[file]["output"])

                # Check the target file exists
                if not os.path.exists(source_path):
                    click.echo(f"Target file {source_path} does not exist")
                    continue

                # Build command line, with necessary options
                # TODO -x is deprecated, find replacement?
                less_command = [less, "-x", source_path, output_path]

                self.command_caller.call(less_command)

        return command
コード例 #12
0
ファイル: dev.py プロジェクト: zarecor60/OctoPrint
class OctoPrintDevelCommands(click.MultiCommand):
	"""
	Custom `click.MultiCommand <http://click.pocoo.org/5/api/#click.MultiCommand>`_
	implementation that provides commands relevant for (plugin) development
	based on availability of development dependencies.
	"""

	sep = ":"
	groups = ("plugin",)

	def __init__(self, *args, **kwargs):
		click.MultiCommand.__init__(self, *args, **kwargs)

		from octoprint.util.commandline import CommandlineCaller
		from functools import partial

		def log_util(f):
			def log(*lines):
					for line in lines:
						f(line)
			return log

		self.command_caller = CommandlineCaller()
		self.command_caller.on_log_call = log_util(lambda x: click.echo(">> {}".format(x)))
		self.command_caller.on_log_stdout = log_util(click.echo)
		self.command_caller.on_log_stderr = log_util(partial(click.echo, err=True))

	def _get_prefix_methods(self, method_prefix):
		for name in [x for x in dir(self) if x.startswith(method_prefix)]:
			method = getattr(self, name)
			yield method

	def _get_commands_from_prefix_methods(self, method_prefix):
		for method in self._get_prefix_methods(method_prefix):
			result = method()
			if result is not None and isinstance(result, click.Command):
				yield result

	def _get_commands(self):
		result = dict()
		for group in self.groups:
			for command in self._get_commands_from_prefix_methods("{}_".format(group)):
				result[group + self.sep + command.name] = command
		return result

	def list_commands(self, ctx):
		result = [name for name in self._get_commands()]
		result.sort()
		return result

	def get_command(self, ctx, cmd_name):
		commands = self._get_commands()
		return commands.get(cmd_name, None)

	def plugin_new(self):
		try:
			import cookiecutter.main
		except ImportError:
			return None

		try:
			# we depend on Cookiecutter >= 1.4
			from cookiecutter.prompt import StrictEnvironment
		except ImportError:
			return None

		import contextlib

		@contextlib.contextmanager
		def custom_cookiecutter_config(config):
			"""
			Allows overriding cookiecutter's user config with a custom dict
			with fallback to the original data.
			"""
			from octoprint.util import fallback_dict

			original_get_user_config = cookiecutter.main.get_user_config
			try:
				def f(*args, **kwargs):
					original_config = original_get_user_config(*args, **kwargs)
					return fallback_dict(config, original_config)
				cookiecutter.main.get_user_config = f
				yield
			finally:
				cookiecutter.main.get_user_config = original_get_user_config

		@contextlib.contextmanager
		def custom_cookiecutter_prompt(options):
			"""
			Custom cookiecutter prompter for the template config.

			If a setting is available in the provided options (read from the CLI)
			that will be used, otherwise the user will be prompted for a value
			via click.
			"""
			original_prompt_for_config = cookiecutter.main.prompt_for_config

			def custom_prompt_for_config(context, no_input=False):
				cookiecutter_dict = dict()

				env = StrictEnvironment()

				for key, raw in context['cookiecutter'].items():
					if key in options:
						val = options[key]
					else:
						if not isinstance(raw, basestring):
							raw = str(raw)
						val = env.from_string(raw).render(cookiecutter=cookiecutter_dict)

						if not no_input:
							val = click.prompt(key, default=val)

					cookiecutter_dict[key] = val
				return cookiecutter_dict

			try:
				cookiecutter.main.prompt_for_config = custom_prompt_for_config
				yield
			finally:
				cookiecutter.main.prompt_for_config = original_prompt_for_config

		@click.command("new")
		@click.option("--name", "-n", help="The name of the plugin")
		@click.option("--package", "-p", help="The plugin package")
		@click.option("--author", "-a", help="The plugin author's name")
		@click.option("--email", "-e", help="The plugin author's mail address")
		@click.option("--license", "-l", help="The plugin's license")
		@click.option("--description", "-d", help="The plugin's description")
		@click.option("--homepage", help="The plugin's homepage URL")
		@click.option("--source", "-s", help="The URL to the plugin's source")
		@click.option("--installurl", "-i", help="The plugin's install URL")
		@click.argument("identifier", required=False)
		def command(name, package, author, email, description, license, homepage, source, installurl, identifier):
			"""Creates a new plugin based on the OctoPrint Plugin cookiecutter template."""
			from octoprint.util import tempdir

			# deleting a git checkout folder might run into access errors due
			# to write-protected sub folders, so we use a custom onerror handler
			# that tries to fix such permissions
			def onerror(func, path, exc_info):
				"""Originally from http://stackoverflow.com/a/2656405/2028598"""
				import stat
				import os

				if not os.access(path, os.W_OK):
					os.chmod(path, stat.S_IWUSR)
					func(path)
				else:
					raise

			with tempdir(onerror=onerror) as path:
				custom = dict(cookiecutters_dir=path)
				with custom_cookiecutter_config(custom):
					raw_options = dict(
						plugin_identifier=identifier,
						plugin_package=package,
						plugin_name=name,
						full_name=author,
						email=email,
						plugin_description=description,
						plugin_license=license,
						plugin_homepage=homepage,
						plugin_source=source,
						plugin_installurl=installurl
					)
					options = dict((k, v) for k, v in raw_options.items() if v is not None)

					with custom_cookiecutter_prompt(options):
						cookiecutter.main.cookiecutter("gh:OctoPrint/cookiecutter-octoprint-plugin")

		return command

	def plugin_install(self):
		@click.command("install")
		@click.option("--path", help="Path of the local plugin development folder to install")
		def command(path):
			"""
			Installs the local plugin in development mode.

			Note: This can NOT be used to install plugins from remote locations
			such as the plugin repository! It is strictly for local development
			of plugins, to ensure the plugin is installed (editable) into the
			same python environment that OctoPrint is installed under.
			"""

			import os
			import sys

			if not path:
				path = os.getcwd()

			# check if this really looks like a plugin
			if not os.path.isfile(os.path.join(path, "setup.py")):
				click.echo("This doesn't look like an OctoPrint plugin folder")
				sys.exit(1)

			self.command_caller.call([sys.executable, "-m", "pip", "install", "-e", "."], cwd=path)

		return command

	def plugin_uninstall(self):
		@click.command("uninstall")
		@click.argument("name")
		def command(name):
			"""Uninstalls the plugin with the given name."""
			import sys

			lower_name = name.lower()
			if not lower_name.startswith("octoprint_") and not lower_name.startswith("octoprint-"):
				click.echo("This doesn't look like an OctoPrint plugin name")
				sys.exit(1)

			call = [sys.executable, "-m", "pip", "uninstall", "--yes", name]
			self.command_caller.call(call)

		return command
コード例 #13
0
class SystemCommandManager:

    SERVER_RESTART_COMMAND = "serverRestartCommand"
    SYSTEM_RESTART_COMMAND = "systemRestartCommand"
    SYSTEM_SHUTDOWN_COMMAND = "systemShutdownCommand"

    def __init__(self):
        self._logger = logging.getLogger(__name__)
        self._caller = CommandlineCaller()

    def execute(self, command):
        if not command:
            return False

        try:
            # we run this with shell=True since we have to trust whatever
            # our admin configured as command and since we want to allow
            # shell-alike handling here...
            p = self._caller.non_blocking_call(command, shell=True)

            if p is None:
                raise CommandlineError(None, "", "")

            if p.returncode is not None:
                stdout = p.stdout.text if p is not None and p.stdout is not None else ""
                stderr = p.stderr.text if p is not None and p.stderr is not None else ""
                raise CommandlineError(p.returncode, stdout, stderr)
        except CommandlineError:
            raise
        except Exception:
            self._logger.exception(f"Error while executing command: {command}")
            raise CommandlineError(None, "", "")

        return True

    def get_command(self, cmd):
        return settings().get(["server", "commands", cmd])

    def has_command(self, cmd):
        return self.get_command(cmd) is not None

    def get_server_restart_command(self):
        return self.get_command(self.SERVER_RESTART_COMMAND)

    def get_system_restart_command(self):
        return self.get_command(self.SYSTEM_RESTART_COMMAND)

    def get_system_shutdown_command(self):
        return self.get_command(self.SYSTEM_SHUTDOWN_COMMAND)

    def has_server_restart_command(self):
        return self.get_server_restart_command() is not None

    def has_system_restart_command(self):
        return self.get_system_restart_command() is not None

    def has_system_shutdown_command(self):
        return self.get_system_shutdown_command() is not None

    def perform_server_restart(self):
        return self.execute(self.get_server_restart_command())

    def perform_system_restart(self):
        return self.execute(self.get_system_restart_command())

    def perform_system_shutdown(self):
        return self.execute(self.get_system_shutdown_command())
コード例 #14
0
 def __init__(self, simply_print):
     self.simply_print = simply_print
     self._logger = logging.getLogger(
         "octoprint.plugins.SimplyPrint.comm.startup")
     self.startup_thread = None
     self.command_line = CommandlineCaller()
コード例 #15
0
class SimplyPrintStartup:
    def __init__(self, simply_print):
        self.simply_print = simply_print
        self._logger = logging.getLogger(
            "octoprint.plugins.SimplyPrint.comm.startup")
        self.startup_thread = None
        self.command_line = CommandlineCaller()

    def run_startup(self):
        if self.startup_thread is not None and self.startup_thread.is_alive():
            # Startup is in progress, lets leave it this way
            return

        thread = threading.Thread(target=self.startup)
        thread.daemon = True
        thread.start()
        self.startup_thread = thread

    def startup(self):
        # Filter out None values, switch to ''
        no_none = lambda x: x if x is not None else ""

        ip = no_none(self.get_ip())
        pi_model = no_none(self.get_pi_model())
        ssid = no_none(self.get_wifi())
        hostname = no_none(self.get_hostname())
        octoprint_version, octoprint_api_version = self.get_octoprint_version()
        python_version = no_none(self.get_python_version_str())

        public_port = self.get_public_port()
        if public_port and public_port != 80:
            ip = "{}:{}".format(ip, public_port)

        url = "&startup=true" \
              "&device_ip={}" \
              "&pi_model={}" \
              "&wifi_ssid={}" \
              "&hostname={}" \
              "&octoprint_version={}" \
              "&octoprint_api_version={}" \
              "&python_version={}".format(
            url_quote(ip), url_quote(pi_model), url_quote(ssid), url_quote(hostname), url_quote(octoprint_version),
            url_quote(octoprint_api_version), url_quote(python_version)
        )

        request = self.simply_print.ping(url)

    @staticmethod
    def get_hostname():
        return socket.gethostname()

    @staticmethod
    def get_python_version_str():
        version_info = sys.version_info
        return "{version_info[0]}.{version_info[1]}.{version_info[2]}".format(
            **locals())

    @staticmethod
    def get_pi_model():
        try:
            with io.open("/proc/device-tree/model", "rt",
                         encoding="utf-8") as file:
                return file.readline().strip(" \t\r\n\0")
        except:
            return

    def get_wifi(self):
        def iwgetid():
            try:
                returncode, stdout, stderr = self.command_line.checked_call(
                    ["/usr/sbin/iwgetid", "-r"])
            except CommandlineError:
                raise

            return stdout[0].strip("\r\n")

        def iwlist():
            try:
                returncode, stdout, stderr = self.command_line.checked_call(
                    ["/usr/sbin/iwlist", "wlan0", "scan"])
            except CommandlineError:
                raise

            for line in stdout:
                line = line.lstrip()
                if line.startswith("ESSID"):
                    return line.split('"')[1]

        try:
            ssid = iwgetid()
        except CommandlineError:
            self._logger.warning("iwgetid failed")
            ssid = None

        if not ssid:
            try:
                ssid = iwlist()
            except CommandlineError:
                self._logger.warning("iwlist failed, can't get SSID")
                return None

        return ssid

    def get_ip(self):
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect(("8.8.8.8", 80))
            ip = s.getsockname()[0]
        except Exception:
            ip = ""

        if not ip or ip is None or ip == "127.0.1.1":
            try:
                returncode, stdout, stderr = self.command_line.checked_call(
                    ["bash", GET_IP_PATH])
            except CommandlineError:
                return None
            ip = stdout.strip("\r\n ").replace("\n", "")

        return ip

    @staticmethod
    def get_octoprint_version():
        """
        Get OctoPrint version and API version
        :return: (tuple) OctoPrint version, API version
        """
        from octoprint.server.api import VERSION
        from octoprint import __version__
        return __version__, VERSION

    def get_public_port(self):
        # noinspection PyProtectedMember
        return self.simply_print._settings.get_int(["public_port"])
コード例 #16
0
ファイル: timelapse.py プロジェクト: lciscon/OctoPrint
    def _render(self):
        """Rendering runnable."""

        ffmpeg = settings().get(["webcam", "ffmpeg"])
        commandline = settings().get(["webcam", "ffmpegCommandline"])
        bitrate = settings().get(["webcam", "bitrate"])
        if ffmpeg is None or bitrate is None:
            self._logger.warning(
                "Cannot create movie, path to ffmpeg or desired bitrate is unset"
            )
            return

        if self._videocodec == "mpeg2video":
            extension = "mpg"
        else:
            extension = "mp4"

        input = os.path.join(
            self._capture_dir,
            self._capture_format.format(
                prefix=self._prefix,
                postfix=self._postfix if self._postfix is not None else "",
            ),
        )

        output_name = self._output_format.format(
            prefix=self._prefix,
            postfix=self._postfix if self._postfix is not None else "",
            extension=extension,
        )
        temporary = os.path.join(self._output_dir, ".{}".format(output_name))
        output = os.path.join(self._output_dir, output_name)

        for i in range(4):
            if os.path.exists(input % i):
                break
        else:
            self._logger.warning("Cannot create a movie, no frames captured")
            self._notify_callback(
                "fail", output, returncode=0, stdout="", stderr="", reason="no_frames"
            )
            return

        hflip = settings().getBoolean(["webcam", "flipH"])
        vflip = settings().getBoolean(["webcam", "flipV"])
        rotate = settings().getBoolean(["webcam", "rotate90"])

        watermark = None
        if settings().getBoolean(["webcam", "watermark"]):
            watermark = os.path.join(
                os.path.dirname(__file__), "static", "img", "watermark.png"
            )
            if sys.platform == "win32":
                # Because ffmpeg hiccups on windows' drive letters and backslashes we have to give the watermark
                # path a special treatment. Yeah, I couldn't believe it either...
                watermark = watermark.replace("\\", "/").replace(":", "\\\\:")

        # prepare ffmpeg command
        command_str = self._create_ffmpeg_command_string(
            commandline,
            ffmpeg,
            self._fps,
            bitrate,
            self._threads,
            input,
            temporary,
            self._videocodec,
            hflip=hflip,
            vflip=vflip,
            rotate=rotate,
            watermark=watermark,
        )
        self._logger.debug("Executing command: {}".format(command_str))

        with self.render_job_lock:
            try:
                self._notify_callback("start", output)

                self._logger.debug("Parsing ffmpeg output")

                c = CommandlineCaller()
                c.on_log_stderr = self._process_ffmpeg_output
                returncode, stdout_text, stderr_text = c.call(
                    command_str, delimiter=b"\r", buffer_size=512
                )

                self._logger.debug("Done with parsing")

                if returncode == 0:
                    shutil.move(temporary, output)
                    self._notify_callback("success", output)
                else:
                    self._logger.warning(
                        "Could not render movie, got return code %r: %s"
                        % (returncode, stderr_text)
                    )
                    self._notify_callback(
                        "fail",
                        output,
                        returncode=returncode,
                        stdout=stdout_text,
                        stderr=stderr_text,
                        reason="returncode",
                    )
            except Exception:
                self._logger.exception("Could not render movie due to unknown error")
                self._notify_callback("fail", output, reason="unknown")
            finally:
                try:
                    if os.path.exists(temporary):
                        os.remove(temporary)
                except Exception:
                    self._logger.warning(
                        "Could not delete temporary timelapse {}".format(temporary)
                    )
                self._notify_callback("always", output)
コード例 #17
0
 def __init__(self):
     self._logger = logging.getLogger(__name__)
     self._caller = CommandlineCaller()