Merge branch 'maintenance' into devel

Conflicts:
	src/octoprint/plugins/softwareupdate/updaters/pip.py
	src/octoprint/util/pip.py
This commit is contained in:
Gina Häußge 2015-07-05 10:15:14 +02:00
commit 8722cdadcd
11 changed files with 199 additions and 53 deletions

View file

@ -0,0 +1,69 @@
.. _sec-features-access_control:
Access Control
==============
When Access Control is enabled, anonymous users (not logged in) will only see
the read-only parts of the UI which are the following:
* printer state
* available gcode files and stats (upload is disabled)
* temperature
* webcam
* gcode viewer
* terminal output (sending commands is disabled)
* available timelapse movies
* any components provided through plugins which are enabled for anonymous
users
Logged in users will get access to everything besides the Settings and System
Commands, which are admin-only.
If Access Control is disabled, everything is directly accessible. **That also
includes all administrative functionality as well as full control over the
printer!**
Upon first start a configuration wizard is provided which allows configuration
of the first administrator account or alternatively disabling Access Control
(which is **NOT** recommended for systems that are directly accessible via the
Internet!).
.. hint::
If you plan to have your OctoPrint instance accessible over the internet,
**always enable Access Control**.
.. _sec-features-access_control-rerunning_wizard:
Rerunning the wizard
--------------------
In case Access Control was disabled in the configuration wizard, it is
possibly to re-run it by editing ``config.yaml`` [#f1]_ and setting ``firstRun``
in the ``server`` section and ``enabled`` in the ``accessControl`` section to
``true``:
.. code-block-ext:: yaml
accessControl:
enabled: true
# ...
server:
firstRun: true
Then restart the server and connect to the web interface - the wizard should
be shown again.
.. note::
If user accounts were created prior to disabling Access Control and those
user accounts are not to be used any more, remove ``.octoprint/users.yaml``.
If you don't remove this file, the above changes won't lead to the
configuration being shown again, instead Access Control will just be
enabled using the already existing login data. This is to prevent you from
resetting access control by accident.
.. rubric:: Footnotes
.. [#f1] For Linux that will be ``~/.octoprint/config.yaml``, for Windows it will be ``%APPDATA%/OctoPrint/config.yaml`` and for
Mac ``~/Library/Application Support/OctoPrint/config.yaml``

View file

@ -10,7 +10,7 @@ buttons which trigger sending of one or more lines of GCODE to the printer over
parameterization of these commands with values entered by the user to full blown GCODE script templates backed by
`Jinja2 <http://jinja.pocoo.org/>`_.
Custom controls are configured within :ref:`config.yaml <sec-configuration-config_yaml>` in a ``controls`` section which
Custom controls are configured within :ref:`config.yaml <sec-configuration-config_yaml>` [#f1]_ in a ``controls`` section which
basically represents a hierarchical structure of all configured custom controls of various types.
.. note::
@ -94,6 +94,13 @@ button that sends one or more commands to the printer when clicked, displaying o
controls that just serve as *container* for other controls, the latter being identified by having a ``children``
attribute wrapping more controls.
.. hint::
Take a look at the `Custom Control Editor plugin <http://plugins.octoprint.org/plugins/customControl/>`_
which allows you configuring your Custom Controls through OctoPrint's
settings interface without the need to manually edit the configuration
file.
.. _sec-features-custom_controls-types:
Types
@ -278,4 +285,9 @@ Parameterized GCODE Script
G28 X0 Y0
Note the usage of the ``parameters.repetitions`` template variable in the GCODE script template, which will contain
the value selected by the user for the "Go arounds" slider.
the value selected by the user for the "Go arounds" slider.
.. rubric:: Footnotes
.. [#f1] For Linux that will be ``~/.octoprint/config.yaml``, for Windows it will be ``%APPDATA%/OctoPrint/config.yaml`` and for
Mac ``~/Library/Application Support/OctoPrint/config.yaml``

View file

@ -7,6 +7,7 @@ Features
.. toctree::
:maxdepth: 2
accesscontrol.rst
custom_controls.rst
gcode_scripts.rst
action_commands.rst

View file

@ -64,7 +64,7 @@ See :ref:`Developing Plugins <sec-plugins>`.
.. rubric:: Footnotes
.. [#f1] For Linux that will be ``~/.octoprint/plugins``, for Windows it will be ``%APPDATA%/OctoPrint/plugins`` and for
Mac ``~/Library/Application Support/OctoPrint``
Mac ``~/Library/Application Support/OctoPrint/plugins``
.. [#f2] Make sure to use the exact same Python installation for installing the plugin that you also used for
installing & running OctoPrint. For OctoPi this means using ``~/oprint/bin/pip`` for installing plugins
instead of just ``pip``.

View file

@ -6,6 +6,10 @@ Welcome to OctoPrint's documentation!
:alt: The OctoPrint Logo
:align: right
This documentation is still in the process of being migrated from
`OctoPrint's wiki <https://github.com/foosel/OctoPrint/wiki>`_, so also take
a look there!
Contents
========

View file

@ -218,7 +218,7 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
if not on_progress_kwargs:
on_progress_kwargs = dict()
self._cura_logger.info("### Slicing %s to %s using profile stored at %s" % (model_path, machinecode_path, profile_path))
self._cura_logger.info(u"### Slicing %s to %s using profile stored at %s" % (model_path, machinecode_path, profile_path))
engine_settings = self._convert_to_engine(profile_path, printer_profile, posX, posY)
@ -227,16 +227,15 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
return False, "Path to CuraEngine is not configured "
working_dir, _ = os.path.split(executable)
args = ['"%s"' % executable, '-v', '-p']
args = [executable, '-v', '-p']
for k, v in engine_settings.items():
args += ["-s", '"%s=%s"' % (k, str(v))]
args += ['-o', '"%s"' % machinecode_path, '"%s"' % model_path]
args += ["-s", "%s=%s" % (k, str(v))]
args += ["-o", machinecode_path, model_path]
self._logger.info(u"Running %r in %s" % (" ".join(args), working_dir))
import sarge
command = " ".join(args)
self._logger.info("Running %r in %s" % (command, working_dir))
p = sarge.run(command, cwd=working_dir, async=True, stdout=sarge.Capture(), stderr=sarge.Capture())
p = sarge.run(args, cwd=working_dir, async=True, stdout=sarge.Capture(), stderr=sarge.Capture())
p.wait_events()
self._slicing_commands[machinecode_path] = p.commands[0]
@ -254,6 +253,7 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
p.commands[0].poll()
continue
line = octoprint.util.to_unicode(line, errors="replace")
self._cura_logger.debug(line.strip())
if on_progress is not None:
@ -282,14 +282,14 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
#
# with <step_factor> being 0 for "inset", 1 for "skin" and 2 for "export".
if line.startswith("Layer count:") and layer_count is None:
if line.startswith(u"Layer count:") and layer_count is None:
try:
layer_count = float(line[len("Layer count:"):].strip())
layer_count = float(line[len(u"Layer count:"):].strip())
except:
pass
elif line.startswith("Progress:"):
split_line = line[len("Progress:"):].strip().split(":")
elif line.startswith(u"Progress:"):
split_line = line[len(u"Progress:"):].strip().split(":")
if len(split_line) == 3:
step, current_layer, _ = split_line
try:
@ -302,21 +302,21 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
on_progress_kwargs["_progress"] = (step_factor[step] * layer_count + current_layer) / (layer_count * 3)
on_progress(*on_progress_args, **on_progress_kwargs)
elif line.startswith("Print time:"):
elif line.startswith(u"Print time:"):
try:
print_time = int(line[len("Print time:"):].strip())
print_time = int(line[len(u"Print time:"):].strip())
if analysis is None:
analysis = dict()
analysis["estimatedPrintTime"] = print_time
except:
pass
elif line.startswith("Filament:") or line.startswith("Filament2:"):
if line.startswith("Filament:"):
filament_str = line[len("Filament:"):].strip()
elif line.startswith(u"Filament:") or line.startswith(u"Filament2:"):
if line.startswith(u"Filament:"):
filament_str = line[len(u"Filament:"):].strip()
tool_key = "tool0"
else:
filament_str = line[len("Filament2:"):].strip()
filament_str = line[len(u"Filament2:"):].strip()
tool_key = "tool1"
try:
@ -339,20 +339,20 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
with self._job_mutex:
if machinecode_path in self._cancelled_jobs:
self._cura_logger.info("### Cancelled")
self._cura_logger.info(u"### Cancelled")
raise octoprint.slicing.SlicingCancelled()
self._cura_logger.info("### Finished, returncode %d" % p.returncode)
self._cura_logger.info(u"### Finished, returncode %d" % p.returncode)
if p.returncode == 0:
return True, dict(analysis=analysis)
else:
self._logger.warn("Could not slice via Cura, got return code %r" % p.returncode)
self._logger.warn(u"Could not slice via Cura, got return code %r" % p.returncode)
return False, "Got returncode %r" % p.returncode
except octoprint.slicing.SlicingCancelled as e:
raise e
except:
self._logger.exception("Could not slice via Cura, got an unknown error")
self._logger.exception(u"Could not slice via Cura, got an unknown error")
return False, "Unknown error, please consult the log file"
finally:
@ -371,7 +371,7 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
command = self._slicing_commands[machinecode_path]
if command is not None:
command.terminate()
self._logger.info("Cancelled slicing of %s" % machinecode_path)
self._logger.info(u"Cancelled slicing of %s" % machinecode_path)
def _load_profile(self, path):
import yaml

View file

@ -420,13 +420,13 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
return self._pip_caller.execute(*args)
def _log_call(self, *lines):
self._log(lines, prefix=" ", stream="call")
self._log(lines, prefix=u" ", stream="call")
def _log_stdout(self, *lines):
self._log(lines, prefix=">", stream="stdout")
self._log(lines, prefix=u">", stream="stdout")
def _log_stderr(self, *lines):
self._log(lines, prefix="!", stream="stderr")
self._log(lines, prefix=u"!", stream="stderr")
def _log(self, lines, prefix=None, stream=None, strip=True):
if strip:

View file

@ -62,12 +62,12 @@ def perform_update(target, check, target_version, log_cb=None):
install_arg = check["pip"].format(target_version=target_version)
logger.debug("Target: %s, executing pip install %s" % (target, install_arg))
logger.debug(u"Target: %s, executing pip install %s" % (target, install_arg))
pip_args = ["install", check["pip"].format(target_version=target_version, target=target_version)]
pip_caller.execute(*pip_args)
logger.debug("Target: %s, executing pip install %s --ignore-reinstalled --force-reinstall --no-deps" % (target, install_arg))
logger.debug(u"Target: %s, executing pip install %s --ignore-reinstalled --force-reinstall --no-deps" % (target, install_arg))
pip_args += ["--ignore-installed", "--force-reinstall", "--no-deps"]
pip_caller.execute(*pip_args)

View file

@ -44,35 +44,71 @@ def _get_latest_release(user, repo, include_prerelease=False):
return latest["name"], latest["tag_name"]
def _is_current(release_information, compare_type, custom=None):
def _get_sanitized_version(version_string):
if "-" in version_string:
version_string = version_string[:version_string.find("-")]
return version_string
def _get_comparable_version_pkg_resources(version_string, force_base=True):
import pkg_resources
version = pkg_resources.parse_version(version_string)
if force_base:
if isinstance(version, tuple):
# old setuptools
base_version = []
for part in version:
if part.startswith("*"):
break
base_version.append(part)
version = tuple(base_version)
else:
# new setuptools
version = pkg_resources.parse_version(version.base_version)
return version
def _get_comparable_version_semantic(version_string, force_base=True):
import semantic_version
version = semantic_version.Version.coerce(version_string, partial=False)
if force_base:
version_string = "{}.{}.{}".format(version.major, version.minor, version.patch)
version = semantic_version.Version.coerce(version_string, partial=False)
return version
def _is_current(release_information, compare_type, custom=None, force_base=True):
if release_information["remote"]["value"] is None:
return True
if not compare_type in ("python", "semantic", "unequal", "custom") or compare_type == "custom" and custom is None:
compare_type = "python"
sanitized_local = _get_sanitized_version(release_information["local"]["value"])
sanitized_remote = _get_sanitized_version(release_information["remote"]["value"])
try:
if compare_type == "python":
import pkg_resources
local_version = pkg_resources.parse_version(release_information["local"]["value"])
remote_version = pkg_resources.parse_version(release_information["remote"]["value"])
local_version = _get_comparable_version_pkg_resources(sanitized_local, force_base=force_base)
remote_version = _get_comparable_version_pkg_resources(sanitized_remote, force_base=force_base)
return local_version >= remote_version
elif compare_type == "semantic":
import semantic_version
local_version = semantic_version.Version(release_information["local"]["value"])
remote_version = semantic_version.Version(release_information["remote"]["value"])
local_version = _get_comparable_version_semantic(sanitized_local, force_base=force_base)
remote_version = _get_comparable_version_semantic(sanitized_remote, force_base=force_base)
return local_version >= remote_version
elif compare_type == "custom":
return custom(release_information["local"], release_information["remote"])
return custom(sanitized_local, sanitized_remote)
else:
return release_information["local"]["value"] == release_information["remote"]["value"]
return sanitized_local == sanitized_remote
except:
logger.exception("Could not check if version is current due to an error, assuming it is")
return True
@ -82,11 +118,13 @@ def get_latest(target, check, custom_compare=None):
if not "user" in check or not "repo" in check:
raise ConfigurationInvalid("github_release update configuration for %s needs user and repo set" % target)
current = None
if "current" in check:
current = check["current"]
current = check.get("current", None)
include_prerelease = check.get("prerelease", False)
force_base = check.get("force_base", True)
remote_name, remote_tag = _get_latest_release(check["user"], check["repo"], include_prerelease=check["prerelease"] == True if "prerelease" in check else False)
remote_name, remote_tag = _get_latest_release(check["user"],
check["repo"],
include_prerelease=include_prerelease)
compare_type = check["release_compare"] if "release_compare" in check else "python"
information =dict(
@ -96,4 +134,7 @@ def get_latest(target, check, custom_compare=None):
logger.debug("Target: %s, local: %s, remote: %s" % (target, current, remote_tag))
return information, _is_current(information, compare_type, custom=custom_compare)
return information, _is_current(information,
compare_type,
custom=custom_compare,
force_base=force_base)

View file

@ -56,6 +56,11 @@ class GcodeWatchdogHandler(watchdog.events.PatternMatchingEventHandler):
file_wrapper.filename,
file_wrapper,
allow_overwrite=True)
if os.path.exists(path):
try:
os.remove(path)
except:
self._logger.exception("Error while trying to clear a file from the watched folder")
def on_created(self, event):
self._upload(event.src_path)

View file

@ -352,9 +352,7 @@ def silent_remove(file):
def sanitize_ascii(line):
if not isinstance(line, basestring):
raise ValueError("Expected either str or unicode but got {} instead".format(line.__class__.__name__ if line is not None else None))
if isinstance(line, str):
line = unicode(line, 'ascii', 'replace')
return line.encode('ascii', 'replace').rstrip()
return to_unicode(line, encoding="ascii", errors="replace").rstrip()
def filter_non_ascii(line):
@ -369,12 +367,28 @@ def filter_non_ascii(line):
"""
try:
unicode(line, 'ascii').encode('ascii')
to_str(to_unicode(line, encoding="ascii"), encoding="ascii")
return False
except ValueError:
return True
def to_str(s_or_u, encoding="utf-8", errors="strict"):
"""Make sure ``s_or_u`` is a str."""
if isinstance(s_or_u, unicode):
return s_or_u.encode(encoding, errors=errors)
else:
return s_or_u
def to_unicode(s_or_u, encoding="utf-8", errors="strict"):
"""Make sure ``s_or_u`` is a unicode string."""
if isinstance(s_or_u, str):
return s_or_u.decode(encoding, errors=errors)
else:
return s_or_u
def dict_merge(a, b):
"""
Recursively deep-merges two dictionaries.