Merge branch 'devel' into dev/folderSupport
This commit is contained in:
commit
3e3a0b905f
24 changed files with 516 additions and 151 deletions
|
|
@ -53,6 +53,8 @@ date of first contribution):
|
|||
* [Andrew Erickson](https://github.com/aerickson)
|
||||
* [Nicanor Romero Venier](https://github.com/nicanor-romero)
|
||||
* [Thomas Hou](https://github.com/masterhou)
|
||||
* [Mark Bastiaans](https://github.com/markbastiaans)
|
||||
* [Marcel Hellwig](https://github.com/punkkeks)
|
||||
|
||||
OctoPrint started off as a fork of [Cura](https://github.com/daid/Cura) by
|
||||
[Daid Braam](https://github.com/daid). Parts of its communication layer and
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@
|
|||
* It's not possible anymore to select files that are not machinecode files (e.g.
|
||||
GCODE) for printing on the file API.
|
||||
* Changes to a user's personal settings via the UI now propagate across sessions.
|
||||
* [#1047](https://github.com/foosel/OctoPrint/issues/1047) - Fixed 90 degree
|
||||
webcam rotation for iOS Safari.
|
||||
|
||||
## 1.2.6 (2015-09-02)
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ Issue a job command
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/job HTTP/1.1
|
||||
POST /api/job HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
|
@ -54,7 +54,7 @@ Issue a job command
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/job HTTP/1.1
|
||||
POST /api/job HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
|
@ -71,7 +71,7 @@ Issue a job command
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/job HTTP/1.1
|
||||
POST /api/job HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
|
@ -88,7 +88,7 @@ Issue a job command
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/control/job HTTP/1.1
|
||||
POST /api/job HTTP/1.1
|
||||
Host: example.com
|
||||
Content-Type: application/json
|
||||
X-Api-Key: abcdef...
|
||||
|
|
|
|||
|
|
@ -159,6 +159,32 @@ This describes actually four hooks:
|
|||
:param str gcode: Parsed GCODE command, e.g. ``G0`` or ``M110``, may also be None if no known command could be parsed
|
||||
:return: None, 1-tuple, 2-tuple or string, see the description above for details.
|
||||
|
||||
.. _sec-plugins-hook-comm-protocol-gcode-received:
|
||||
|
||||
octoprint.comm.protocol.gcode.received
|
||||
--------------------------------------
|
||||
|
||||
.. py:function:: hook(comm_instance, line, *args, **kwargs)
|
||||
|
||||
Get the returned lines sent by the printer. Handlers should return the received line or in any case, the modified
|
||||
version of it. If the the handler returns None, processing will be aborted and the communication layer will get an
|
||||
empty string as the received line. Note that Python functions will also automatically return ``None`` if an empty
|
||||
``return`` statement is used or just nothing is returned explicitely from the handler.
|
||||
|
||||
**Example:**
|
||||
|
||||
Looks for the response of a M115, which contains information about the MACHINE_TYPE, among other things.
|
||||
|
||||
.. onlineinclude:: https://raw.githubusercontent.com/OctoPrint/Plugin-Examples/master/read_m115_response.py
|
||||
:linenos:
|
||||
:tab-width: 4
|
||||
:caption: `read_m115_response.py <https://github.com/OctoPrint/Plugin-Examples/blob/master/read_m115_response.py>`_
|
||||
|
||||
:param MachineCom comm_instance: The :class:`~octoprint.util.comm.MachineCom` instance which triggered the hook.
|
||||
:param str line: The line received from the printer.
|
||||
:return: The received line or in any case, a modified version of it.
|
||||
:rtype: str
|
||||
|
||||
.. _sec-plugins-hook-comm-protocol-scripts:
|
||||
|
||||
octoprint.comm.protocol.scripts
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ StartupPlugin
|
|||
|
||||
.. autoclass:: octoprint.plugin.StartupPlugin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _sec-plugins-mixins-shutdownplugin:
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ ShutdownPlugin
|
|||
|
||||
.. autoclass:: octoprint.plugin.ShutdownPlugin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _sec-plugins-mixins-settingsplugin:
|
||||
|
||||
|
|
@ -34,6 +36,7 @@ SettingsPlugin
|
|||
|
||||
.. autoclass:: octoprint.plugin.SettingsPlugin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _sec-plugins-mixins-assetplugin:
|
||||
|
||||
|
|
@ -42,6 +45,7 @@ AssetPlugin
|
|||
|
||||
.. autoclass:: octoprint.plugin.AssetPlugin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _sec-plugins-mixins-templateplugin:
|
||||
|
||||
|
|
@ -50,6 +54,7 @@ TemplatePlugin
|
|||
|
||||
.. autoclass:: octoprint.plugin.TemplatePlugin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _sec-plugins-mixins-wizardplugin:
|
||||
|
||||
|
|
@ -58,6 +63,16 @@ WizardPlugin
|
|||
|
||||
.. autoclass:: octoprint.plugin.WizardPlugin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _sec-plugins-mixins-uiplugin:
|
||||
|
||||
UiPlugin
|
||||
--------
|
||||
|
||||
.. autoclass:: octoprint.plugin.UiPlugin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _sec-plugins-mixins-simpleapiplugin:
|
||||
|
||||
|
|
@ -66,6 +81,7 @@ SimpleApiPlugin
|
|||
|
||||
.. autoclass:: octoprint.plugin.SimpleApiPlugin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _sec-plugins-mixins-blueprintplugin:
|
||||
|
||||
|
|
@ -74,6 +90,7 @@ BlueprintPlugin
|
|||
|
||||
.. autoclass:: octoprint.plugin.BlueprintPlugin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _sec-plugins-mixins-eventhandlerplugin:
|
||||
|
||||
|
|
@ -82,6 +99,7 @@ EventHandlerPlugin
|
|||
|
||||
.. autoclass:: octoprint.plugin.EventHandlerPlugin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _sec-plugins-mixins-progressplugin:
|
||||
|
||||
|
|
@ -90,6 +108,7 @@ ProgressPlugin
|
|||
|
||||
.. autoclass:: octoprint.plugin.ProgressPlugin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _sec-plugins-mixins-slicerplugin:
|
||||
|
||||
|
|
@ -98,4 +117,5 @@ SlicerPlugin
|
|||
|
||||
.. autoclass:: octoprint.plugin.SlicerPlugin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
|
|
|||
2
run
2
run
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python2
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python2
|
||||
# coding=utf-8
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python2
|
||||
|
||||
import sys
|
||||
from octoprint.daemon import Daemon
|
||||
from octoprint.server import Server
|
||||
|
|
|
|||
|
|
@ -104,7 +104,8 @@ def plugin_manager(init=False, plugin_folders=None, plugin_types=None, plugin_en
|
|||
SlicerPlugin,
|
||||
AppPlugin,
|
||||
ProgressPlugin,
|
||||
WizardPlugin]
|
||||
WizardPlugin,
|
||||
UiPlugin]
|
||||
if plugin_entry_points is None:
|
||||
plugin_entry_points = "octoprint.plugin"
|
||||
if plugin_disabled_list is None:
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@ way and could be extracted into a separate Python module in the future.
|
|||
.. autoclass:: Plugin
|
||||
:members:
|
||||
|
||||
.. autoclass:: RestartNeedingPlugin
|
||||
:members:
|
||||
|
||||
.. autoclass:: SortablePlugin
|
||||
:members:
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
|
@ -1077,11 +1083,12 @@ class PluginManager(object):
|
|||
except:
|
||||
self.logger.exception("Error while trying to retrieve sorting order for plugin {}".format(impl[0]))
|
||||
|
||||
try:
|
||||
int(sorting_value)
|
||||
except ValueError:
|
||||
self.logger.warn("The order value returned by {} for sorting context {} is not a valid integer, ignoring it".format(impl[0], sorting_context))
|
||||
sorting_value = None
|
||||
if sorting_value is not None:
|
||||
try:
|
||||
int(sorting_value)
|
||||
except ValueError:
|
||||
self.logger.warn("The order value returned by {} for sorting context {} is not a valid integer, ignoring it".format(impl[0], sorting_context))
|
||||
sorting_value = None
|
||||
|
||||
return sorting_value is None, sorting_value, impl[0]
|
||||
|
||||
|
|
@ -1279,10 +1286,34 @@ class Plugin(object):
|
|||
pass
|
||||
|
||||
class RestartNeedingPlugin(Plugin):
|
||||
pass
|
||||
"""
|
||||
Mixin for plugin types that need a restart in order to be enabled.
|
||||
"""
|
||||
|
||||
class SortablePlugin(Plugin):
|
||||
"""
|
||||
Mixin for plugin types that are sortable.
|
||||
"""
|
||||
|
||||
def get_sorting_key(self, context=None):
|
||||
"""
|
||||
Returns the sorting key to use for the implementation in the specified ``context``.
|
||||
|
||||
May return ``None`` if order is irrelevant.
|
||||
|
||||
Implementations returning None will be ordered by plugin identifier
|
||||
after all implementations which did return a sorting key value that was
|
||||
not None sorted by that.
|
||||
|
||||
Arguments:
|
||||
context (str): The sorting context for which to provide the
|
||||
sorting key value.
|
||||
|
||||
Returns:
|
||||
int or None: An integer signifying the sorting key value of the plugin
|
||||
(sorting will be done ascending), or None if the implementation
|
||||
doesn't care about calling order.
|
||||
"""
|
||||
return None
|
||||
|
||||
class PluginNeedsRestart(Exception):
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ Please note that the plugin implementation types are documented in the section
|
|||
.. autoclass:: OctoPrintPlugin
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: ReloadNeedingPlugin
|
||||
:show-inheritance:
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
|
@ -77,7 +80,7 @@ class OctoPrintPlugin(Plugin):
|
|||
and if not creating it before returning it. Injected by the plugin core system upon initialization of the
|
||||
implementation.
|
||||
|
||||
.. automethod:: get_plugin_data_folder
|
||||
.. automethod:: get_plugin_data_folder
|
||||
"""
|
||||
|
||||
def get_plugin_data_folder(self):
|
||||
|
|
@ -97,12 +100,18 @@ class OctoPrintPlugin(Plugin):
|
|||
|
||||
|
||||
class ReloadNeedingPlugin(Plugin):
|
||||
pass
|
||||
"""
|
||||
Mixin for plugin types that need a reload of the UI in order to become usable.
|
||||
"""
|
||||
|
||||
class StartupPlugin(OctoPrintPlugin, SortablePlugin):
|
||||
"""
|
||||
The ``StartupPlugin`` allows hooking into the startup of OctoPrint. It can be used to start up additional services
|
||||
on or just after the startup of the server.
|
||||
|
||||
``StartupPlugin`` is a :class:`~octoprint.plugin.core.SortablePlugin`. The
|
||||
relevant sorting context for :meth:`on_startup` is ``StartupPlugin.on_startup``,
|
||||
the one for :meth:`on_after_startup` will be ``StartupPlugin.on_after_startup``.
|
||||
"""
|
||||
|
||||
def on_startup(self, host, port):
|
||||
|
|
@ -132,6 +141,9 @@ class ShutdownPlugin(OctoPrintPlugin, SortablePlugin):
|
|||
The ``ShutdownPlugin`` allows hooking into the shutdown of OctoPrint. It's usually used in conjunction with the
|
||||
:class:`StartupPlugin` mixin, to cleanly shut down additional services again that where started by the :class:`StartupPlugin`
|
||||
part of the plugin.
|
||||
|
||||
``ShutdownPlugin`` is a :class:`~octoprint.plugin.core.SortablePlugin`.
|
||||
The relevant sorting context will be ``ShutdownPlugin.on_shutdown``.
|
||||
"""
|
||||
|
||||
def on_shutdown(self):
|
||||
|
|
@ -149,6 +161,8 @@ class AssetPlugin(OctoPrintPlugin, RestartNeedingPlugin):
|
|||
|
||||
A typical usage of the ``AssetPlugin`` functionality is to embed a custom view model to be used by templates injected
|
||||
through a :class:`TemplatePlugin`.
|
||||
|
||||
``AssetPlugin`` is a :class:`~octoprint.plugins.core.RestartNeedingPlugin`.
|
||||
"""
|
||||
|
||||
def get_asset_folder(self):
|
||||
|
|
@ -297,6 +311,8 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin):
|
|||
responsibility to ensure that all core functionality is still maintained.
|
||||
|
||||
Plugins can also add additional template types by implementing the :ref:`octoprint.ui.web.templatetypes <sec-plugins-hook-ui-web-templatetypes>` hook.
|
||||
|
||||
``TemplatePlugin`` is a :class:`~octoprint.plugin.core.ReloadNeedingPlugin`.
|
||||
"""
|
||||
|
||||
def get_template_configs(self):
|
||||
|
|
@ -485,6 +501,167 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin):
|
|||
return os.path.join(self._basefolder, "templates")
|
||||
|
||||
|
||||
class UiPlugin(OctoPrintPlugin, SortablePlugin):
|
||||
"""
|
||||
The ``UiPlugin`` mixin allows plugins to completely replace the UI served
|
||||
by OctoPrint when requesting the main page hosted at `/`.
|
||||
|
||||
OctoPrint will query whether your mixin implementation will handle a
|
||||
provided request by calling :meth:`~octoprint.plugin.UiPlugin.will_handle_ui` with the Flask
|
||||
`Request <http://flask.pocoo.org/docs/0.10/api/#flask.Request>`_ object as
|
||||
parameter. If you plugin returns `True` here, OctoPrint will next call
|
||||
:meth:`~octoprint.plugin.UiPlugin.on_ui_render` with a couple of parameters like
|
||||
- again - the Flask Request object and the render keyword arguments as
|
||||
used by the default OctoPrint web interface. For more information see below.
|
||||
|
||||
There are two methods used in order to allow for caching of the actual
|
||||
response sent to the client. Whatever a plugin implementation returns
|
||||
from the call to its :meth:`~octoprint.plugin.UiPlugin.on_ui_render` method
|
||||
will be cached server side. The cache will be emptied in case of explicit
|
||||
no-cache headers sent by the client, or if the ``_refresh`` query parameter
|
||||
on the request exists and is set to ``true``. To prevent caching of the
|
||||
response altogether, a plugin may set no-cache headers on the returned
|
||||
response as well.
|
||||
|
||||
``UiPlugin`` is a :class:`~octoprint.plugin.core.SortablePlugin`. The
|
||||
relevant sorting context when acting as a UiPlugin is ``UiPlugin.will_handle_ui``.
|
||||
The first plugin to return ``True`` will be the one whose ui will be used,
|
||||
no further calls to :meth:`~octoprint.plugin.UiPlugin.on_ui_render` will be performed.
|
||||
|
||||
If implementations want to serve custom templates in the :meth:`~octoprint.plugin.UiPlugin.on_ui_render`
|
||||
method it is recommended to also implement the :class:`~octoprint.plugin.TemplatePlugin`
|
||||
mixin.
|
||||
|
||||
**Example**
|
||||
|
||||
What follows is a very simple example that renders a different (non functional and
|
||||
only exemplary) UI if the requesting client has a UserAgent string hinting
|
||||
at it being a mobile device:
|
||||
|
||||
.. onlineinclude:: https://raw.githubusercontent.com/OctoPrint/Plugin-Examples/master/dummy_mobile_ui/__init__.py
|
||||
:linenos:
|
||||
:tab-width: 4
|
||||
:caption: `dummy_mobile_ui/__init__.py <https://github.com/OctoPrint/Plugin-Examples/blob/master/dummy_mobile_ui/__init__.py>`_
|
||||
|
||||
.. onlineinclude:: https://raw.githubusercontent.com/OctoPrint/Plugin-Examples/master/dummy_mobile_ui/templates/dummy_mobile_ui_index.jinja2
|
||||
:linenos:
|
||||
:tab-width: 4
|
||||
:caption: `dummy_mobile_ui/templates/dummy_mobile_ui_index.jinja2 <https://github.com/OctoPrint/Plugin-Examples/blob/master/dummy_mobile_ui/templates/dummy_mobile_ui_index.jinja2>`_
|
||||
|
||||
Try installing the above plugin ``dummy_mobile_ui`` (also available in the
|
||||
`plugin examples repository <https://github.com/OctoPrint/Plugin-Examples/blob/master/dummy_mobile_ui>`_)
|
||||
into your OctoPrint instance. If you access it from a regular desktop browser,
|
||||
you should still see the default UI. However if you access it from a mobile
|
||||
device (make sure to not have that request the desktop version of pages!)
|
||||
you should see the very simple dummy page defined above.
|
||||
"""
|
||||
|
||||
def will_handle_ui(self, request):
|
||||
"""
|
||||
Called by OctoPrint to determine if the mixin implementation will be
|
||||
able to handle the ``request`` provided as a parameter.
|
||||
|
||||
Return ``True`` here to signal that your implementation will handle
|
||||
the request and that the result of its :meth:`~octoprint.plugin.UiPlugin.on_ui_render` method
|
||||
is what should be served to the user.
|
||||
|
||||
Arguments:
|
||||
request (flask.Request): A Flask `Request <http://flask.pocoo.org/docs/0.10/api/#flask.Request>`_
|
||||
object.
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if the the implementation will serve the request,
|
||||
``False`` otherwise.
|
||||
"""
|
||||
return False
|
||||
|
||||
def on_ui_render(self, now, request, render_kwargs):
|
||||
"""
|
||||
Called by OctoPrint to retrieve the response to send to the client
|
||||
for the ``request`` to ``/``. Only called if :meth:`~octoprint.plugin.UiPlugin.will_handle_ui`
|
||||
returned ``True``.
|
||||
|
||||
``render_kwargs`` will be a dictionary (whose contents are cached) which
|
||||
will contain the following key and value pairs (note that not all
|
||||
key value pairs contained in the dictionary are listed here, only
|
||||
those you should depend on as a plugin developer at the current time):
|
||||
|
||||
.. list-table::
|
||||
:widths: 5 95
|
||||
|
||||
* - debug
|
||||
- ``True`` if debug mode is enabled, ``False`` otherwise.
|
||||
* - firstRun
|
||||
- ``True`` if the server is being run for the first time (not
|
||||
configured yet), ``False`` otherwise.
|
||||
* - version
|
||||
- OctoPrint's version information. This is a ``dict`` with the
|
||||
following keys:
|
||||
|
||||
.. list-table::
|
||||
:widths: 5 95
|
||||
|
||||
* - number
|
||||
- The version number (e.g. ``x.y.z``)
|
||||
* - branch
|
||||
- The GIT branch from which the OctoPrint instance was built
|
||||
(e.g. ``master``)
|
||||
* - display
|
||||
- The full human readable version string, including the
|
||||
branch information (e.g. ``x.y.z (master branch)``
|
||||
|
||||
* - uiApiKey
|
||||
- The UI API key to use for unauthorized API requests. This is
|
||||
freshly generated on every server restart.
|
||||
* - templates
|
||||
- Template data to render in the UI. Will be a ``dict`` containing entries
|
||||
for all known template types.
|
||||
|
||||
The sub structure for each key will be as follows:
|
||||
|
||||
.. list-table::
|
||||
:widths: 5 95
|
||||
|
||||
* - order
|
||||
- A list of template names in the order they should appear
|
||||
in the final rendered page
|
||||
* - entries
|
||||
- The template entry definitions to render. Depending on the
|
||||
template type those are either 2-tuples of a name and a ``dict``
|
||||
or directly ``dicts`` with information regarding the
|
||||
template to render.
|
||||
|
||||
For the possible contents of the data ``dicts`` see the
|
||||
:class:`~octoprint.plugin.TemplatePlugin` mixin.
|
||||
|
||||
* - pluginNames
|
||||
- A list of names of :class:`~octoprint.plugin.TemplatePlugin`
|
||||
implementation that were enabled when creating the ``templates``
|
||||
value.
|
||||
* - locales
|
||||
- The locales for which there are translations available.
|
||||
|
||||
On top of that all additional template variables as provided by :meth:`~octoprint.plugin.TemplatePlugin.get_template_vars`
|
||||
will be contained in the dictionary as well.
|
||||
|
||||
Arguments:
|
||||
now (datetime.datetime): The datetime instance representing "now"
|
||||
for this request, in case your plugin implementation needs this
|
||||
information.
|
||||
request (flask.Request): A Flask `Request <http://flask.pocoo.org/docs/0.10/api/#flask.Request>`_ object.
|
||||
render_kwargs (dict): The (cached) render keyword arguments that
|
||||
would usually be provided to the core UI render function.
|
||||
|
||||
Returns:
|
||||
flask.Response: Should return a Flask `Response <http://flask.pocoo.org/docs/0.10/api/#flask.Response>`_
|
||||
object that can be served to the requesting client directly. May be
|
||||
created with ``flask.make_response`` combined with something like
|
||||
``flask.render_template``.
|
||||
"""
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class WizardPlugin(OctoPrintPlugin, ReloadNeedingPlugin):
|
||||
"""
|
||||
The ``WizardPlugin`` mixin allows plugins to report to OctoPrint whether
|
||||
|
|
@ -546,6 +723,8 @@ class WizardPlugin(OctoPrintPlugin, ReloadNeedingPlugin):
|
|||
|
||||
def get_wizard_version(self):
|
||||
return 1
|
||||
|
||||
``WizardPlugin`` is a :class:`~octoprint.plugin.core.ReloadNeedingPlugin`.
|
||||
"""
|
||||
|
||||
def is_wizard_required(self):
|
||||
|
|
@ -850,6 +1029,8 @@ class BlueprintPlugin(OctoPrintPlugin, RestartNeedingPlugin):
|
|||
|
||||
flask.url_for("plugin.myblueprintplugin.myEcho") # will return "/plugin/myblueprintplugin/echo"
|
||||
|
||||
|
||||
``BlueprintPlugin`` implements :class:`~octoprint.plugins.core.RestartNeedingPlugin`.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -874,6 +1055,24 @@ class BlueprintPlugin(OctoPrintPlugin, RestartNeedingPlugin):
|
|||
return f
|
||||
return decorator
|
||||
|
||||
@staticmethod
|
||||
def errorhandler(code_or_exception):
|
||||
"""
|
||||
A decorator to mark errorhandlings methods in your BlueprintPlugin subclass. Works just the same as Flask's
|
||||
own ``errorhandler`` decorator available on blueprints.
|
||||
|
||||
See `the documentation for flask.Blueprint.errorhandler <http://flask.pocoo.org/docs/0.10/api/#flask.Blueprint.errorhandler>`_
|
||||
and `the documentation for flask.Flask.errorhandler <http://flask.pocoo.org/docs/0.10/api/#flask.Flask.errorhandler>`_ for more
|
||||
information.
|
||||
"""
|
||||
from collections import defaultdict
|
||||
def decorator(f):
|
||||
if not hasattr(f, "_blueprint_error_handler") or f._blueprint_error_handler is None:
|
||||
f._blueprint_error_handler = defaultdict(list)
|
||||
f._blueprint_error_handler[f.__name__].append(code_or_exception)
|
||||
return f
|
||||
return decorator
|
||||
|
||||
def get_blueprint(self):
|
||||
"""
|
||||
Creates and returns the blueprint for your plugin. Override this if you want to define and handle your blueprint yourself.
|
||||
|
|
@ -892,6 +1091,9 @@ class BlueprintPlugin(OctoPrintPlugin, RestartNeedingPlugin):
|
|||
for blueprint_rule in f._blueprint_rules[member]:
|
||||
rule, options = blueprint_rule
|
||||
blueprint.add_url_rule(rule, options.pop("endpoint", f.__name__), view_func=f, **options)
|
||||
if hasattr(f, "_blueprint_error_handler") and member in f._blueprint_error_handler:
|
||||
for code_or_exception in f._blueprint_error_handler[member]:
|
||||
blueprint.errorhandler(code_or_exception)(f)
|
||||
return blueprint
|
||||
|
||||
def get_blueprint_kwargs(self):
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
self._refresh_configured_checks = False
|
||||
self._configured_checks = self._settings.get(["checks"], merged=True)
|
||||
update_check_hooks = self._plugin_manager.get_hooks("octoprint.plugin.softwareupdate.check_config")
|
||||
check_providers = self._settings.get(["check_providers"], merged=True)
|
||||
for name, hook in update_check_hooks.items():
|
||||
try:
|
||||
hook_checks = hook()
|
||||
|
|
@ -82,9 +83,23 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
self._logger.exception("Error while retrieving update information from plugin {name}".format(**locals()))
|
||||
else:
|
||||
for key, data in hook_checks.items():
|
||||
check_providers[key] = name
|
||||
if key in self._configured_checks:
|
||||
data = dict_merge(data, self._configured_checks[key])
|
||||
self._configured_checks[key] = data
|
||||
self._settings.set(["check_providers"], check_providers)
|
||||
self._settings.save()
|
||||
|
||||
# we only want to process checks that came from plugins for
|
||||
# which the plugins are still installed and enabled
|
||||
config_checks = self._settings.get(["checks"])
|
||||
plugin_and_not_enabled = lambda k: k in check_providers and \
|
||||
not check_providers[k] in self._plugin_manager.enabled_plugins
|
||||
obsolete_plugin_checks = filter(plugin_and_not_enabled,
|
||||
config_checks.keys())
|
||||
for key in obsolete_plugin_checks:
|
||||
self._logger.debug("Check for key {} was provided by plugin {} that's no longer available, ignoring it".format(key, check_providers[key]))
|
||||
del self._configured_checks[key]
|
||||
|
||||
return self._configured_checks
|
||||
|
||||
|
|
@ -150,6 +165,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
},
|
||||
},
|
||||
"pip_command": None,
|
||||
"check_providers": {},
|
||||
|
||||
"cache_ttl": 24 * 60,
|
||||
}
|
||||
|
|
@ -416,14 +432,13 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
if not target in check_targets:
|
||||
continue
|
||||
|
||||
populated_check = self._populated_check(target, check)
|
||||
|
||||
try:
|
||||
populated_check = self._populated_check(target, check)
|
||||
target_information, target_update_available, target_update_possible = self._get_current_version(target, populated_check, force=force)
|
||||
if target_information is None:
|
||||
target_information = dict()
|
||||
except exceptions.UnknownCheckType:
|
||||
self._logger.warn("Unknown update check type for %s" % target)
|
||||
self._logger.warn("Unknown update check type for target {}: {}".format(target, check.get("type", "<n/a>")))
|
||||
continue
|
||||
|
||||
target_information = dict_merge(dict(local=dict(name="unknown", value="unknown"), remote=dict(name="unknown", value="unknown")), target_information)
|
||||
|
|
@ -669,6 +684,9 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
raise exceptions.RestartFailed()
|
||||
|
||||
def _populated_check(self, target, check):
|
||||
if not "type" in check:
|
||||
raise exceptions.UnknownCheckType()
|
||||
|
||||
result = dict(check)
|
||||
|
||||
if target == "octoprint":
|
||||
|
|
@ -703,30 +721,6 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
def _send_client_message(self, message_type, data=None):
|
||||
self._plugin_manager.send_plugin_message(self._identifier, dict(type=message_type, data=data))
|
||||
|
||||
def _populated_check(self, target, check):
|
||||
result = dict(check)
|
||||
|
||||
if target == "octoprint":
|
||||
from flask.ext.babel import gettext
|
||||
result["displayName"] = check.get("displayName", gettext("OctoPrint"))
|
||||
result["displayVersion"] = check.get("displayVersion", "{octoprint_version}")
|
||||
|
||||
from octoprint._version import get_versions
|
||||
versions = get_versions()
|
||||
if check["type"] == "github_commit":
|
||||
result["current"] = versions.get("full-revisionid", versions.get("full", "unknown"))
|
||||
else:
|
||||
result["current"] = versions["version"]
|
||||
else:
|
||||
result["displayName"] = check.get("displayName", target)
|
||||
result["displayVersion"] = check.get("displayVersion", check.get("current", "unknown"))
|
||||
if check["type"] in ("github_commit"):
|
||||
result["current"] = check.get("current", None)
|
||||
else:
|
||||
result["current"] = check.get("current", check.get("displayVersion", None))
|
||||
|
||||
return result
|
||||
|
||||
def _get_version_checker(self, target, check):
|
||||
"""
|
||||
Retrieves the version checker to use for given target and check configuration. Will raise an UnknownCheckType
|
||||
|
|
|
|||
|
|
@ -7,25 +7,34 @@ __copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms
|
|||
|
||||
import octoprint.plugin
|
||||
|
||||
|
||||
class VirtualPrinterPlugin(octoprint.plugin.SettingsPlugin):
|
||||
def virtual_printer_factory(self, comm_instance, port, baudrate,
|
||||
read_timeout):
|
||||
if not port == "VIRTUAL":
|
||||
return None
|
||||
|
||||
def virtual_printer_factory(self, comm_instance, port, baudrate, read_timeout):
|
||||
if not port == "VIRTUAL":
|
||||
return None
|
||||
if not self._settings.global_get_boolean(
|
||||
["devel", "virtualPrinter", "enabled"]):
|
||||
return None
|
||||
|
||||
if not self._settings.global_get_boolean(["devel", "virtualPrinter", "enabled"]):
|
||||
return None
|
||||
import logging
|
||||
import logging.handlers
|
||||
|
||||
import logging
|
||||
import logging.handlers
|
||||
seriallog_handler = logging.handlers.RotatingFileHandler(
|
||||
self._settings.get_plugin_logfile_path(postfix="serial"),
|
||||
maxBytes=2 * 1024 * 1024)
|
||||
seriallog_handler.setFormatter(
|
||||
logging.Formatter("%(asctime)s %(message)s"))
|
||||
seriallog_handler.setLevel(logging.DEBUG)
|
||||
|
||||
seriallog_handler = logging.handlers.RotatingFileHandler(self._settings.get_plugin_logfile_path(postfix="serial"), maxBytes=2*1024*1024)
|
||||
seriallog_handler.setFormatter(logging.Formatter("%(asctime)s %(message)s"))
|
||||
seriallog_handler.setLevel(logging.DEBUG)
|
||||
from . import virtual
|
||||
|
||||
serial_obj = virtual.VirtualPrinter(
|
||||
seriallog_handler=seriallog_handler,
|
||||
read_timeout=float(read_timeout))
|
||||
return serial_obj
|
||||
|
||||
from . import virtual
|
||||
serial_obj = virtual.VirtualPrinter(seriallog_handler=seriallog_handler, read_timeout=float(read_timeout))
|
||||
return serial_obj
|
||||
|
||||
__plugin_name__ = "Virtual Printer"
|
||||
__plugin_author__ = "Gina Häußge, based on work by Daid Braam"
|
||||
|
|
@ -33,13 +42,14 @@ __plugin_homepage__ = "https://github.com/foosel/OctoPrint/wiki/Plugin:-Virtual-
|
|||
__plugin_license__ = "AGPLv3"
|
||||
__plugin_description__ = "Provides a virtual printer via a virtual serial port for development and testing purposes"
|
||||
|
||||
|
||||
def __plugin_load__():
|
||||
plugin = VirtualPrinterPlugin()
|
||||
plugin = VirtualPrinterPlugin()
|
||||
|
||||
global __plugin_implementation__
|
||||
__plugin_implementation__ = plugin
|
||||
global __plugin_implementation__
|
||||
__plugin_implementation__ = plugin
|
||||
|
||||
global __plugin_hooks__
|
||||
__plugin_hooks__ = {
|
||||
"octoprint.comm.transport.serial.factory": plugin.virtual_printer_factory
|
||||
}
|
||||
global __plugin_hooks__
|
||||
__plugin_hooks__ = {
|
||||
"octoprint.comm.transport.serial.factory": plugin.virtual_printer_factory
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ class VirtualPrinter(object):
|
|||
self._deleteSdFile(filename)
|
||||
elif "M114" in data:
|
||||
# send dummy position report
|
||||
output = "C: X:10.00 Y:3.20 Z:5.20 E:1.24"
|
||||
output = "C: X:{} Y:{} Z:{} E:{}".format(self._lastX, self._lastY, self._lastZ, self._lastE)
|
||||
if not self._okBeforeCommandOutput:
|
||||
output = "ok " + output
|
||||
self._send(output)
|
||||
|
|
@ -243,6 +243,8 @@ class VirtualPrinter(object):
|
|||
elif "M117" in data:
|
||||
# we'll just use this to echo a message, to allow playing around with pause triggers
|
||||
self._send("echo:%s" % re.search("M117\s+(.*)", data).group(1))
|
||||
elif "M400" in data:
|
||||
self.buffered.join()
|
||||
elif "M999" in data:
|
||||
# mirror Marlin behaviour
|
||||
self._send("Resend: 1")
|
||||
|
|
@ -674,6 +676,7 @@ class VirtualPrinter(object):
|
|||
continue
|
||||
|
||||
self._performMove(line)
|
||||
self.buffered.task_done()
|
||||
|
||||
def write(self, data):
|
||||
if self._debug_drop_connection:
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ def on_identity_loaded(sender, identity):
|
|||
if user is None:
|
||||
return
|
||||
|
||||
identity.provides.add(UserNeed(user.get_name()))
|
||||
identity.provides.add(UserNeed(user.get_id()))
|
||||
if user.is_user():
|
||||
identity.provides.add(RoleNeed("user"))
|
||||
if user.is_admin():
|
||||
|
|
@ -99,9 +99,9 @@ def load_user(id):
|
|||
|
||||
if userManager is not None:
|
||||
if sessionid:
|
||||
return userManager.findUser(username=id, session=sessionid)
|
||||
return userManager.findUser(userid=id, session=sessionid)
|
||||
else:
|
||||
return userManager.findUser(username=id)
|
||||
return userManager.findUser(userid=id)
|
||||
return users.DummyUser()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -227,8 +227,10 @@ def passive_login():
|
|||
user = flask.ext.login.current_user
|
||||
|
||||
if user is not None and not user.is_anonymous():
|
||||
flask.g.user = user
|
||||
flask.ext.principal.identity_changed.send(flask.current_app._get_current_object(), identity=flask.ext.principal.Identity(user.get_id()))
|
||||
if hasattr(user, "get_session"):
|
||||
flask.session["usersession.id"] = user.get_session()
|
||||
flask.g.user = user
|
||||
return flask.jsonify(user.asDict())
|
||||
elif settings().getBoolean(["accessControl", "autologinLocal"]) \
|
||||
and settings().get(["accessControl", "autologinAs"]) is not None \
|
||||
|
|
@ -252,7 +254,7 @@ def passive_login():
|
|||
logger = logging.getLogger(__name__)
|
||||
logger.exception("Could not autologin user %s for networks %r" % (autologinAs, localNetworks))
|
||||
|
||||
return ("", 204)
|
||||
return "", 204
|
||||
|
||||
|
||||
#~~ cache decorator for cacheable views
|
||||
|
|
@ -281,11 +283,11 @@ def cached(timeout=5 * 60, key=lambda: "view/%s" % flask.request.path, unless=No
|
|||
if not callable(refreshif) or not refreshif():
|
||||
rv = _cache.get(cache_key)
|
||||
if rv is not None:
|
||||
logger.debug("Serving entry for {path} from cache".format(path=flask.request.path))
|
||||
logger.debug("Serving entry for {path} from cache (key: {key})".format(path=flask.request.path, key=cache_key))
|
||||
return rv
|
||||
|
||||
# get value from wrapped function
|
||||
logger.debug("No cache entry or refreshing cache for {path}, calling wrapped function".format(path=flask.request.path))
|
||||
logger.debug("No cache entry or refreshing cache for {path} (key: {key}), calling wrapped function".format(path=flask.request.path, key=cache_key))
|
||||
rv = f(*args, **kwargs)
|
||||
|
||||
# do not store if the "unless_response" condition is true
|
||||
|
|
|
|||
|
|
@ -22,21 +22,95 @@ from . import util
|
|||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@app.route("/")
|
||||
@util.flask.cached(refreshif=lambda: util.flask.cache_check_headers() or "_refresh" in request.values,
|
||||
key=lambda: "view/%s/%s" % (request.path, g.locale),
|
||||
unless_response=util.flask.cache_check_response_headers)
|
||||
def index():
|
||||
_templates = None
|
||||
_plugin_names = None
|
||||
_plugin_vars = None
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
force_refresh = util.flask.cache_check_headers() or "_refresh" in request.values
|
||||
|
||||
global _templates, _plugin_names, _plugin_vars
|
||||
|
||||
if force_refresh or _templates is None or _plugin_names is None or _plugin_vars is None:
|
||||
_templates, _plugin_names, _plugin_vars = _process_templates()
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
render_kwargs = _get_render_kwargs(_templates, _plugin_names, _plugin_vars, now)
|
||||
|
||||
def get_cached_view(key, view):
|
||||
return util.flask.cached(refreshif=lambda: force_refresh,
|
||||
key=lambda: "ui:{}:{}".format(key, g.locale),
|
||||
unless_response=util.flask.cache_check_response_headers)(view)
|
||||
|
||||
ui_plugins = pluginManager.get_implementations(octoprint.plugin.UiPlugin, sorting_context="UiPlugin.on_ui_render")
|
||||
for plugin in ui_plugins:
|
||||
if plugin.will_handle_ui(request):
|
||||
# plugin claims responsibility, let it render the UI
|
||||
cached = get_cached_view(plugin._identifier, plugin.on_ui_render)
|
||||
response = cached(now, request, render_kwargs)
|
||||
if response is not None:
|
||||
break
|
||||
|
||||
else:
|
||||
wizard = bool(_templates["wizard"]["order"])
|
||||
enable_accesscontrol = userManager is not None
|
||||
|
||||
render_kwargs.update(dict(
|
||||
webcamStream=settings().get(["webcam", "stream"]),
|
||||
enableTemperatureGraph=settings().get(["feature", "temperatureGraph"]),
|
||||
enableAccessControl=enable_accesscontrol,
|
||||
enableSdSupport=settings().get(["feature", "sdSupport"]),
|
||||
gcodeMobileThreshold=settings().get(["gcodeViewer", "mobileSizeThreshold"]),
|
||||
gcodeThreshold=settings().get(["gcodeViewer", "sizeThreshold"]),
|
||||
wizard=wizard,
|
||||
now=now,
|
||||
))
|
||||
|
||||
# no plugin took an interest, we'll use the default UI
|
||||
def make_default_ui():
|
||||
r = make_response(render_template("index.jinja2", **render_kwargs))
|
||||
if bool(render_kwargs["templates"]["wizard"]["order"]):
|
||||
r = util.flask.add_non_caching_response_headers(r)
|
||||
return r
|
||||
|
||||
cached = get_cached_view("_default", make_default_ui)
|
||||
response = cached()
|
||||
|
||||
response.headers["Last-Modified"] = now
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def _get_render_kwargs(templates, plugin_names, plugin_vars, now):
|
||||
#~~ a bunch of settings
|
||||
|
||||
first_run = settings().getBoolean(["server", "firstRun"])
|
||||
locales = dict((l.language, dict(language=l.language, display=l.display_name, english=l.english_name)) for l in LOCALES)
|
||||
|
||||
#~~ prepare full set of template vars for rendering
|
||||
|
||||
render_kwargs = dict(
|
||||
debug=debug,
|
||||
firstRun=first_run,
|
||||
version=dict(number=VERSION, display=DISPLAY_VERSION, branch=BRANCH),
|
||||
uiApiKey=UI_API_KEY,
|
||||
templates=templates,
|
||||
pluginNames=plugin_names,
|
||||
locales=locales,
|
||||
)
|
||||
render_kwargs.update(plugin_vars)
|
||||
|
||||
return render_kwargs
|
||||
|
||||
|
||||
def _process_templates():
|
||||
enable_accesscontrol = userManager is not None
|
||||
first_run = settings().getBoolean(["server", "firstRun"])
|
||||
enable_gcodeviewer = settings().getBoolean(["gcodeViewer", "enabled"])
|
||||
enable_timelapse = (settings().get(["webcam", "snapshot"]) and settings().get(["webcam", "ffmpeg"]))
|
||||
enable_systemmenu = settings().get(["system"]) is not None and settings().get(["system", "actions"]) is not None and len(settings().get(["system", "actions"])) > 0
|
||||
enable_accesscontrol = userManager is not None
|
||||
preferred_stylesheet = settings().get(["devel", "stylesheet"])
|
||||
locales = dict((l.language, dict(language=l.language, display=l.display_name, english=l.english_name)) for l in LOCALES)
|
||||
|
||||
##~~ prepare templates
|
||||
|
||||
|
|
@ -327,43 +401,7 @@ def index():
|
|||
templates[t]["entries"].update(template_sorting[t]["custom_insert_entries"](sorted_missing))
|
||||
templates[t]["order"] = template_sorting[t]["custom_insert_order"](templates[t]["order"], sorted_missing)
|
||||
|
||||
#~~ prepare full set of template vars for rendering
|
||||
|
||||
wizard = bool(templates["wizard"]["order"])
|
||||
now = datetime.datetime.utcnow()
|
||||
render_kwargs = dict(
|
||||
webcamStream=settings().get(["webcam", "stream"]),
|
||||
enableTemperatureGraph=settings().get(["feature", "temperatureGraph"]),
|
||||
enableAccessControl=enable_accesscontrol,
|
||||
enableSdSupport=settings().get(["feature", "sdSupport"]),
|
||||
firstRun=first_run,
|
||||
debug=debug,
|
||||
version=VERSION,
|
||||
display_version=DISPLAY_VERSION,
|
||||
branch=BRANCH,
|
||||
gcodeMobileThreshold=settings().get(["gcodeViewer", "mobileSizeThreshold"]),
|
||||
gcodeThreshold=settings().get(["gcodeViewer", "sizeThreshold"]),
|
||||
uiApiKey=UI_API_KEY,
|
||||
templates=templates,
|
||||
pluginNames=plugin_names,
|
||||
locales=locales,
|
||||
wizard=wizard,
|
||||
now=now
|
||||
)
|
||||
render_kwargs.update(plugin_vars)
|
||||
|
||||
#~~ render!
|
||||
|
||||
response = make_response(render_template(
|
||||
"index.jinja2",
|
||||
**render_kwargs
|
||||
))
|
||||
response.headers["Last-Modified"] = now
|
||||
|
||||
if wizard:
|
||||
response = util.flask.add_non_caching_response_headers(response)
|
||||
|
||||
return response
|
||||
return templates, plugin_names, plugin_vars
|
||||
|
||||
|
||||
def _process_template_configs(name, implementation, configs, rules):
|
||||
|
|
@ -454,7 +492,8 @@ def robotsTxt():
|
|||
|
||||
|
||||
@app.route("/i18n/<string:locale>/<string:domain>.js")
|
||||
@util.flask.cached(refreshif=lambda: util.flask.cache_check_headers() or "_refresh" in request.values, key=lambda: "view/%s/%s" % (request.path, g.locale))
|
||||
@util.flask.cached(refreshif=lambda: util.flask.cache_check_headers() or "_refresh" in request.values,
|
||||
key=lambda: "{}:{}".format(request.path, g.locale))
|
||||
def localeJs(locale, domain):
|
||||
messages = dict()
|
||||
plural_expr = None
|
||||
|
|
|
|||
|
|
@ -1072,7 +1072,7 @@ class Settings(object):
|
|||
def saveScript(self, script_type, name, script):
|
||||
script_folder = self.getBaseFolder("scripts")
|
||||
filename = os.path.realpath(os.path.join(script_folder, script_type, name))
|
||||
if not filename.startswith(script_folder):
|
||||
if not filename.startswith(os.path.realpath(script_folder)):
|
||||
# oops, jail break, that shouldn't happen
|
||||
raise ValueError("Invalid script path to save to: {filename} (from {script_type}:{name})".format(**locals()))
|
||||
|
||||
|
|
|
|||
|
|
@ -585,7 +585,7 @@ class SlicingManager(object):
|
|||
name = self._sanitize(name)
|
||||
|
||||
path = os.path.join(self.get_slicer_profile_path(slicer), "{name}.profile".format(name=name))
|
||||
if not os.path.realpath(path).startswith(self._profile_path):
|
||||
if not os.path.realpath(path).startswith(os.path.realpath(self._profile_path)):
|
||||
raise IOError("Path to profile {name} tried to break out of allows sub path".format(**locals()))
|
||||
if must_exist and not (os.path.exists(path) and os.path.isfile(path)):
|
||||
raise UnknownProfile(slicer, name)
|
||||
|
|
|
|||
|
|
@ -914,6 +914,7 @@ textarea.block {
|
|||
}
|
||||
|
||||
.rotate90 {
|
||||
-webkit-transform: rotate(-90deg);
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@
|
|||
var SOCKJS_CLOSE_NORMAL = 1000;
|
||||
|
||||
var UI_API_KEY = "{{ uiApiKey }}";
|
||||
var VERSION = "{{ version }}";
|
||||
var DISPLAY_VERSION = "{{ display_version }}";
|
||||
var BRANCH = "{{ branch }}";
|
||||
var VERSION = "{{ version.number }}";
|
||||
var DISPLAY_VERSION = "{{ version.display }}";
|
||||
var BRANCH = "{{ version.branch }}";
|
||||
var LOCALE = "{{ g.locale }}";
|
||||
var AVAILABLE_LOCALES = {{ locales|tojson }};
|
||||
|
||||
|
|
|
|||
|
|
@ -23,23 +23,29 @@ class UserManager(object):
|
|||
def __init__(self):
|
||||
self._logger = logging.getLogger(__name__)
|
||||
self._session_users_by_session = dict()
|
||||
self._session_users_by_username = dict()
|
||||
self._session_users_by_userid = dict()
|
||||
|
||||
def login_user(self, user):
|
||||
self._cleanup_sessions()
|
||||
|
||||
if user is None \
|
||||
or (isinstance(user, LocalProxy) and not isinstance(user._get_current_object(), User)) \
|
||||
or (not isinstance(user, LocalProxy) and not isinstance(user, User)):
|
||||
if user is None:
|
||||
return
|
||||
|
||||
if isinstance(user, LocalProxy):
|
||||
user = user._get_current_object()
|
||||
|
||||
if not isinstance(user, User):
|
||||
return None
|
||||
|
||||
if not isinstance(user, SessionUser):
|
||||
user = SessionUser(user)
|
||||
|
||||
self._session_users_by_session[user.get_session()] = user
|
||||
|
||||
if not user.get_name() in self._session_users_by_username:
|
||||
self._session_users_by_username[user.get_name()] = []
|
||||
self._session_users_by_username[user.get_name()].append(user)
|
||||
userid = user.get_id()
|
||||
if not userid in self._session_users_by_userid:
|
||||
self._session_users_by_userid[userid] = []
|
||||
self._session_users_by_userid[userid].append(user)
|
||||
|
||||
self._logger.debug("Logged in user: %r" % user)
|
||||
|
||||
|
|
@ -49,14 +55,18 @@ class UserManager(object):
|
|||
if user is None:
|
||||
return
|
||||
|
||||
if isinstance(user, LocalProxy):
|
||||
user = user._get_current_object()
|
||||
|
||||
if not isinstance(user, SessionUser):
|
||||
return
|
||||
|
||||
if user.get_name() in self._session_users_by_username:
|
||||
users_by_username = self._session_users_by_username[user.get_name()]
|
||||
for u in users_by_username:
|
||||
userid = user.get_id()
|
||||
if userid in self._session_users_by_userid:
|
||||
users_by_userid = self._session_users_by_userid[userid]
|
||||
for u in users_by_userid:
|
||||
if u.get_session() == user.get_session():
|
||||
users_by_username.remove(u)
|
||||
users_by_userid.remove(u)
|
||||
break
|
||||
|
||||
if user.get_session() in self._session_users_by_session:
|
||||
|
|
@ -137,21 +147,19 @@ class UserManager(object):
|
|||
pass
|
||||
|
||||
def removeUser(self, username):
|
||||
if username in self._session_users_by_username:
|
||||
users = self._session_users_by_username[username]
|
||||
if username in self._session_users_by_userid:
|
||||
users = self._session_users_by_userid[username]
|
||||
sessions = [user.get_session() for user in users if isinstance(user, SessionUser)]
|
||||
for session in sessions:
|
||||
if session in self._session_users_by_session:
|
||||
del self._session_users_by_session[session]
|
||||
del self._session_users_by_username[username]
|
||||
del self._session_users_by_userid[username]
|
||||
|
||||
def findUser(self, username=None, session=None):
|
||||
if session is not None:
|
||||
for session in self._session_users_by_session:
|
||||
user = self._session_users_by_session[session]
|
||||
if username is None or username == user.get_id():
|
||||
return user
|
||||
break
|
||||
def findUser(self, userid=None, session=None):
|
||||
if session is not None and session in self._session_users_by_session:
|
||||
user = self._session_users_by_session[session]
|
||||
if userid is None or userid == user.get_id():
|
||||
return user
|
||||
|
||||
return None
|
||||
|
||||
|
|
@ -345,16 +353,16 @@ class FilebasedUserManager(UserManager):
|
|||
self._dirty = True
|
||||
self._save()
|
||||
|
||||
def findUser(self, username=None, apikey=None, session=None):
|
||||
user = UserManager.findUser(self, username=username, session=session)
|
||||
def findUser(self, userid=None, apikey=None, session=None):
|
||||
user = UserManager.findUser(self, userid=userid, session=session)
|
||||
|
||||
if user is not None:
|
||||
return user
|
||||
|
||||
if username is not None:
|
||||
if username not in self._users.keys():
|
||||
if userid is not None:
|
||||
if userid not in self._users.keys():
|
||||
return None
|
||||
return self._users[username]
|
||||
return self._users[userid]
|
||||
|
||||
elif apikey is not None:
|
||||
for user in self._users.values():
|
||||
|
|
@ -413,7 +421,7 @@ class User(UserMixin):
|
|||
return self._passwordHash == passwordHash
|
||||
|
||||
def get_id(self):
|
||||
return self._username
|
||||
return self.get_name()
|
||||
|
||||
def get_name(self):
|
||||
return self._username
|
||||
|
|
|
|||
|
|
@ -270,6 +270,7 @@ class MachineCom(object):
|
|||
sending=self._pluginManager.get_hooks("octoprint.comm.protocol.gcode.sending"),
|
||||
sent=self._pluginManager.get_hooks("octoprint.comm.protocol.gcode.sent")
|
||||
)
|
||||
self._received_message_hooks = self._pluginManager.get_hooks("octoprint.comm.protocol.gcode.received")
|
||||
|
||||
self._printer_action_hooks = self._pluginManager.get_hooks("octoprint.comm.protocol.action")
|
||||
self._gcodescript_hooks = self._pluginManager.get_hooks("octoprint.comm.protocol.scripts")
|
||||
|
|
@ -1416,9 +1417,6 @@ class MachineCom(object):
|
|||
self._errorValue = get_exception_string()
|
||||
self.close(is_error=True)
|
||||
return None
|
||||
if ret == '':
|
||||
#self._log("Recv: TIMEOUT")
|
||||
return ''
|
||||
|
||||
try:
|
||||
self._log("Recv: %s" % sanitize_ascii(ret))
|
||||
|
|
@ -1426,6 +1424,15 @@ class MachineCom(object):
|
|||
self._log("WARN: While reading last line: %s" % e)
|
||||
self._log("Recv: %r" % ret)
|
||||
|
||||
for name, hook in self._received_message_hooks.items():
|
||||
try:
|
||||
ret = hook(self, ret)
|
||||
except:
|
||||
self._logger.exception("Error while processing hook {name}:".format(**locals()))
|
||||
else:
|
||||
if ret is None:
|
||||
return ""
|
||||
|
||||
return ret
|
||||
|
||||
def _getNext(self):
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ class gcode(object):
|
|||
absoluteE = True
|
||||
scale = 1.0
|
||||
posAbs = True
|
||||
fwretractTime = 0
|
||||
fwretractDist = 0
|
||||
fwrecoverTime = 0
|
||||
feedRateXY = min(printer_profile["axes"]["x"]["speed"], printer_profile["axes"]["y"]["speed"])
|
||||
if feedRateXY == 0:
|
||||
# some somewhat sane default if axes speeds are insane...
|
||||
|
|
@ -172,6 +175,10 @@ class gcode(object):
|
|||
P = getCodeFloat(line, 'P')
|
||||
if P is not None:
|
||||
totalMoveTimeMinute += P / 60.0 / 1000.0
|
||||
elif G == 10: #Firmware retract
|
||||
totalMoveTimeMinute += fwretractTime
|
||||
elif G == 11: #Firmware retract recover
|
||||
totalMoveTimeMinute += fwrecoverTime
|
||||
elif G == 20: #Units are inches
|
||||
scale = 25.4
|
||||
elif G == 21: #Units are mm
|
||||
|
|
@ -214,6 +221,15 @@ class gcode(object):
|
|||
absoluteE = True
|
||||
elif M == 83: #Relative E
|
||||
absoluteE = False
|
||||
elif M == 207 or M == 208: #Firmware retract settings
|
||||
s = getCodeFloat(line, 'S')
|
||||
f = getCodeFloat(line, 'F')
|
||||
if s is not None and f is not None:
|
||||
if M == 207:
|
||||
fwretractTime = s / f
|
||||
fwretractDist = s
|
||||
else:
|
||||
fwrecoverTime = (fwretractDist + s) / f
|
||||
|
||||
elif T is not None:
|
||||
if T > settings().getInt(["gcodeAnalysis", "maxExtruders"]):
|
||||
|
|
|
|||
Loading…
Reference in a new issue