Ejemplo n.º 1
0
def load_identity():
    if not hasattr(g, "current_user") or not g.current_user:
        logging.debug("User identity as Anonymous")
        return p.AnonymousIdentity()
    identity = p.Identity(g.current_user.id)
    identity.user = g.current_user
    logging.debug("Current user %s %d " %
                  (g.current_user.name, g.current_user.id))
    identity.provides.add(p.UserNeed(g.current_user.id))
    if g.current_user.permissions.count():
        for role in g.current_user.permissions:
            identity.provides.add(p.RoleNeed(role.name.upper()))
        logging.debug(
            "User has permissions: %s" %
            (", ".join([role.name for role in g.current_user.permissions])))
    else:
        logging.debug("User Has no permissions")
        pass
    return identity
Ejemplo n.º 2
0
    def on_identity_loaded(sender, identity):  # pylint: disable=locally-disabled,unused-variable,unused-argument
        """
        Flask-Principal signal callback for populating user identity object after
        login.
        """
        # Set the identity user object.
        identity.user = flask_login.current_user

        if not flask_login.current_user.is_authenticated:
            flask.current_app.logger.debug(
                "Loaded ACL identity for anonymous user '{}'.".format(
                    str(flask_login.current_user)))
            return
        flask.current_app.logger.debug(
            "Loading ACL identity for user '{}'.".format(
                str(flask_login.current_user)))

        # Add the UserNeed to the identity.
        if hasattr(flask_login.current_user, 'get_id'):
            identity.provides.add(
                flask_principal.UserNeed(flask_login.current_user.id))

        # Assuming the UserModel has a list of roles, update the identity with
        # the roles that the user provides.
        if hasattr(flask_login.current_user, 'roles'):
            for role in flask_login.current_user.roles:
                identity.provides.add(flask_principal.RoleNeed(role))

        # Assuming the UserModel has a list of group memberships, update the
        # identity with the groups that the user is member of.
        if hasattr(flask_login.current_user, 'memberships'):
            for group in flask_login.current_user.memberships:
                identity.provides.add(mydojo.auth.MembershipNeed(group.id))

        # Assuming the UserModel has a list of group managements, update the
        # identity with the groups that the user is manager of.
        if hasattr(flask_login.current_user, 'managements'):
            for group in flask_login.current_user.managements:
                identity.provides.add(mydojo.auth.ManagementNeed(group.id))
Ejemplo n.º 3
0
class ArcWelderPlugin(
        octoprint.plugin.StartupPlugin,
        octoprint.plugin.TemplatePlugin,
        octoprint.plugin.SettingsPlugin,
        octoprint.plugin.AssetPlugin,
        octoprint.plugin.BlueprintPlugin,
):

    if LooseVersion(octoprint.server.VERSION) >= LooseVersion("1.4"):
        import octoprint.access.permissions as permissions

        admin_permission = permissions.Permissions.ADMIN
    else:
        import flask_principal

        admin_permission = flask_principal.Permission(
            flask_principal.RoleNeed("admin"))

    def __init__(self):
        super(ArcWelderPlugin, self).__init__()
        self.preprocessing_job_guid = None
        self.preprocessing_job_source_file_name = ""
        self.preprocessing_job_target_file_name = ""
        self.is_cancelled = False
        self.settings_default = dict(
            use_octoprint_settings=True,
            g90_g91_influences_extruder=False,
            resolution_mm=0.05,
            overwrite_source_file=False,
            target_prefix="AS_",
            enabled=True,
            logging_configuration=dict(
                default_log_level=log.ERROR,
                log_to_console=False,
                enabled_loggers=[],
            ),
            version=__version__,
            git_version=__git_version__,
        )

    def on_after_startup(self):
        logging_configurator.configure_loggers(self._log_file_path,
                                               self._logging_configuration)
        logger.info("Startup Complete.")

    # Events
    def get_settings_defaults(self):
        # plugin_version is not instantiated when __init__ is called.  Update it now.
        self.settings_default["version"] = self._plugin_version
        return self.settings_default

    def on_settings_save(self, data):
        octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
        # reconfigure logging
        logging_configurator.configure_loggers(self._log_file_path,
                                               self._logging_configuration)

    def get_template_configs(self):
        return [
            dict(
                type="settings",
                custom_bindings=True,
                template="arc_welder_settings.jinja2",
            )
        ]

    # Blueprints
    @octoprint.plugin.BlueprintPlugin.route("/cancelPreprocessing",
                                            methods=["POST"])
    @restricted_access
    def cancel_preprocessing_request(self):
        with ArcWelderPlugin.admin_permission.require(http_exception=403):
            request_values = request.get_json()
            preprocessing_job_guid = request_values["preprocessing_job_guid"]
            if self.preprocessing_job_guid is None or preprocessing_job_guid != str(
                    self.preprocessing_job_guid):
                # return without doing anything, this job is already over
                return jsonify({"success": True})

            logger.info("Cancelling Preprocessing for /cancelPreprocessing.")
            self.preprocessing_job_guid = None
            self.is_cancelled = True

            self.send_pre_processing_progress_message(100, 0, 0, 0, 0, 0, 0)
            return jsonify({"success": True})

    @octoprint.plugin.BlueprintPlugin.route("/clearLog", methods=["POST"])
    @restricted_access
    def clear_log_request(self):
        with ArcWelderPlugin.admin_permission.require(http_exception=403):
            request_values = request.get_json()
            clear_all = request_values["clear_all"]
            if clear_all:
                logger.info("Clearing all log files.")
            else:
                logger.info("Rolling over most recent log.")

            logging_configurator.do_rollover(clear_all=clear_all)
            return jsonify({"success": True})

    # Callback Handler for /downloadFile
    # uses the ArcWelderLargeResponseHandler
    def download_file_request(self, request_handler):
        download_file_path = None
        # get the args
        file_type = request_handler.get_query_arguments('type')[0]
        if file_type == 'log':
            full_path = self._log_file_path
        if full_path is None or not os.path.isfile(full_path):
            raise tornado.web.HTTPError(404)
        return full_path

    def send_notification_toast(self,
                                toast_type,
                                title,
                                message,
                                auto_close,
                                key=None,
                                close_keys=[]):
        data = {
            "message_type": "toast",
            "toast_type": toast_type,
            "title": title,
            "message": message,
            "auto_close": auto_close,
            "key": key,
            "close_keys": close_keys,
        }
        self._plugin_manager.send_plugin_message(self._identifier, data)
        # return true to continue, false to terminate

    def send_preprocessing_start_message(self):
        data = {
            "message_type": "preprocessing-start",
            "preprocessing_job_guid": self.preprocessing_job_guid,
            "source_filename": self.preprocessing_job_source_file_name,
            "target_filename": self.preprocessing_job_target_file_name,
        }
        self._plugin_manager.send_plugin_message(self._identifier, data)

    def send_pre_processing_progress_message(
        self,
        percent_progress,
        seconds_elapsed,
        seconds_to_complete,
        gcodes_processed,
        lines_processed,
        points_compressed,
        arcs_created,
    ):
        data = {
            "message_type": "preprocessing-progress",
            "percent_progress": percent_progress,
            "seconds_elapsed": seconds_elapsed,
            "seconds_to_complete": seconds_to_complete,
            "gcodes_processed": gcodes_processed,
            "lines_processed": lines_processed,
            "points_compressed": points_compressed,
            "arcs_created": arcs_created,
            "source_filename": self.preprocessing_job_source_file_name,
            "target_filename": self.preprocessing_job_target_file_name,
        }
        self._plugin_manager.send_plugin_message(self._identifier, data)
        # sleep for just a bit to allow the plugin message time to be sent and for cancel messages to arrive
        # the real answer for this is to figure out how to allow threading in the C++ code
        return not self.is_cancelled

    # ~~ AssetPlugin mixin
    def get_assets(self):
        # Define your plugin's asset files to automatically include in the
        # core UI here.
        return dict(
            js=["js/arc_welder.js", "js/arc_welder.settings.js"],
            css=["css/arc_welder.css"],
        )

    # Properties
    @property
    def _log_file_path(self):
        return self._settings.get_plugin_logfile_path()

    # Settings Properties
    @property
    def _logging_configuration(self):
        logging_configurator = self._settings.get(["logging_configuration"])
        if logging_configurator is None:
            logging_configurator = self.settings_default[
                "logging_configuration"]
        return logging_configurator

    @property
    def _gcode_conversion_log_level(self):
        if "enabled_loggers" not in self._logging_configuration:
            return log.ERROR
        else:
            enabled_loggers = self._logging_configuration["enabled_loggers"]
        log_level = log.CRITICAL
        for logger in enabled_loggers:
            if logger["name"] == "arc_welder.gcode_conversion":
                log_level = logger["log_level"]
                break
        return log_level

    @property
    def _enabled(self):
        enabled = self._settings.get_boolean(["enabled"])
        if enabled is None:
            enabled = self.settings_default["enabled"]
        return enabled

    @property
    def _use_octoprint_settings(self):
        use_octoprint_settings = self._settings.get_boolean(
            ["use_octoprint_settings"])
        if use_octoprint_settings is None:
            use_octoprint_settings = self.settings_default[
                "use_octoprint_settings"]
        return use_octoprint_settings

    @property
    def _g90_g91_influences_extruder(self):
        if self._use_octoprint_settings:
            g90_g91_influences_extruder = self._settings.global_get(
                ["feature", "g90InfluencesExtruder"])
        else:
            g90_g91_influences_extruder = self._settings.get_boolean(
                ["g90_g91_influences_extruder"])
        if g90_g91_influences_extruder is None:
            g90_g91_influences_extruder = self.settings_default[
                "g90_g91_influences_extruder"]
        return g90_g91_influences_extruder

    @property
    def _resolution_mm(self):
        resolution_mm = self._settings.get_float(["resolution_mm"])
        if resolution_mm is None:
            resolution_mm = self.settings_default["resolution_mm"]
        return resolution_mm

    @property
    def _overwrite_source_file(self):
        overwrite_source_file = self._settings.get_boolean(
            ["overwrite_source_file"])
        if overwrite_source_file is None:
            overwrite_source_file = self.settings_default[
                "overwrite_source_file"]
        return overwrite_source_file

    @property
    def _target_prefix(self):
        target_prefix = self._settings.get(["target_prefix"])
        if target_prefix is None:
            target_prefix = ""
        else:
            target_prefix = target_prefix.strip()
        if len(target_prefix) == 0:
            target_prefix = self.settings_default["target_prefix"]
        return target_prefix

    def get_storage_path_and_name(self, storage_path, add_prefix):
        path, name = self._file_manager.split_path(FileDestinations.LOCAL,
                                                   storage_path)
        if add_prefix:
            new_name = self._target_prefix + name
        else:
            new_name = name
        new_path = self._file_manager.join_path(FileDestinations.LOCAL, path,
                                                new_name)
        return new_path, new_name

    def get_preprocessor_arguments(self, source_path_on_disk):
        target_file_path = os.path.join(self.get_plugin_data_folder(),
                                        "temp.gcode")
        return {
            "source_file_path": source_path_on_disk,
            "target_file_path": target_file_path,
            "resolution_mm": self._resolution_mm,
            "g90_g91_influences_extruder": self._g90_g91_influences_extruder,
            "on_progress_received": self.send_pre_processing_progress_message,
            "log_level": self._gcode_conversion_log_level
        }

    # hooks
    def preprocessor(self, path, file_object, links, printer_profile,
                     allow_overwrite, *args, **kwargs):
        if self._printer.is_printing():
            self.send_notification_toast(
                "error",
                "Arc-Welder: Unable to Process",
                "Cannot preprocess gcode while a print is in progress because print quality may be affected.",
                False,
                key="unable_to_process",
                close_keys=["unable_to_process"])
            return
        if hasattr(file_object, "arc_welder"):
            return file_object

        if not self._enabled or not octoprint.filemanager.valid_file_type(
                path, type="gcode"):
            return file_object
        elif not allow_overwrite:
            logger.info(
                "Received a new gcode file for processing, but allow_overwrite is set to false.  FileName: %s.",
                file_object.filename)
            return file_object

        logger.info("Received a new gcode file for processing.  FileName: %s.",
                    file_object.filename)

        self.is_cancelled = False
        self.preprocessing_job_guid = str(uuid.uuid4())
        new_path, new_name = self.get_storage_path_and_name(
            path, not self._overwrite_source_file)
        self.preprocessing_job_source_file_name = file_object.filename
        self.preprocessing_job_target_file_name = new_name
        arc_converter_args = self.get_preprocessor_arguments(file_object.path)
        self.send_preprocessing_start_message()
        logger.info(
            "Starting pre-processing with the following arguments:\n\tsource_file_path: "
            "%s\n\ttarget_file_path: %s\n\tresolution_mm: %.3f\n\tg90_g91_influences_extruder: %r"
            "\n\tlog_level: %d", arc_converter_args["source_file_path"],
            arc_converter_args["target_file_path"],
            arc_converter_args["resolution_mm"],
            arc_converter_args["g90_g91_influences_extruder"],
            arc_converter_args["log_level"])

        # this will contain metadata results from the ConvertFile call
        result = None
        try:
            result = converter.ConvertFile(arc_converter_args)
        except Exception as e:
            self.send_notification_toast(
                "error",
                "Arc Welder Preprocessing Failed",
                "An error occurred while preprocessing {0}.  Check plugin_arc_welder.log for details.",
                False,
            )
            logger.exception("Unable to convert the gcode file.")
            raise e
        #finally:
        #self.send_pre_processing_progress_message(200, 0, 0, 0, 0, 0, 0)

        if self._overwrite_source_file:
            logger.info("Arc compression complete, overwriting source file.")
            # return the modified file
            return octoprint.filemanager.util.DiskFileWrapper(
                path, arc_converter_args["target_file_path"], move=True)
        else:
            logger.info(
                "Arc compression complete, creating a new gcode file: %s",
                new_name)
            new_file_object = octoprint.filemanager.util.DiskFileWrapper(
                new_name, arc_converter_args["target_file_path"], move=True)
            setattr(new_file_object, "arc_welder", True)
            self._file_manager.add_file(
                FileDestinations.LOCAL,
                new_path,
                new_file_object,
                allow_overwrite=True,
                display=new_name,
            )
            # return the original object
            return file_object

    def register_custom_routes(self, server_routes, *args, **kwargs):
        # version specific permission validator
        if LooseVersion(octoprint.server.VERSION) >= LooseVersion("1.4"):
            admin_validation_chain = [
                util.tornado.access_validation_factory(
                    app, util.flask.admin_validator),
            ]
        else:
            # the concept of granular permissions does not exist in this version of Octoprint.  Fallback to the
            # admin role
            def admin_permission_validator(flask_request):
                user = util.flask.get_flask_user_from_request(flask_request)
                if user is None or not user.is_authenticated(
                ) or not user.is_admin():
                    raise tornado.web.HTTPError(403)

            permission_validator = admin_permission_validator
            admin_validation_chain = [
                util.tornado.access_validation_factory(app,
                                                       permission_validator),
            ]
        return [(r"/downloadFile", ArcWelderLargeResponseHandler,
                 dict(request_callback=self.download_file_request,
                      as_attachment=True,
                      access_validation=util.tornado.validation_chain(
                          *admin_validation_chain)))]

        # ~~ software update hook

    def get_update_information(self):
        # get the checkout type from the software updater
        prerelease_channel = None
        is_prerelease = False
        # get this for reference.  Eventually I'll have to use it!
        # is the software update set to prerelease?

        if self._settings.global_get(
            ["plugins", "softwareupdate", "checks", "octoprint",
             "prerelease"]):
            # If it's a prerelease, look at the channel and configure the proper branch for Arc Welder
            prerelease_channel = self._settings.global_get([
                "plugins", "softwareupdate", "checks", "octoprint",
                "prerelease_channel"
            ])
            if prerelease_channel == "rc/maintenance":
                is_prerelease = True
                prerelease_channel = "rc/maintenance"
            elif prerelease_channel == "rc/devel":
                is_prerelease = True
                prerelease_channel = "rc/devel"

        arc_welder_info = dict(
            displayName="Arc Welder: Anti-Stutter",
            displayVersion=self._plugin_version,
            # version check: github repository
            type="github_release",
            user="******",
            repo="ArcWelderPlugin",
            current=self._plugin_version,
            prerelease=is_prerelease,
            pip=
            "https://github.com/FormerLurker/ArcWelderPlugin/archive/{target_version}.zip",
            stable_branch=dict(branch="master",
                               commitish=["master"],
                               name="Stable"),
            release_compare='semantic_version',
            prerelease_branches=[
                dict(
                    branch="rc/maintenance",
                    commitish=["rc/maintenance"],  # maintenance RCs
                    name="Maintenance RCs"),
                dict(
                    branch="rc/devel",
                    commitish=["rc/maintenance",
                               "rc/devel"],  # devel & maintenance RCs
                    name="Devel RCs")
            ],
        )

        if prerelease_channel is not None:
            arc_welder_info["prerelease_channel"] = prerelease_channel
        # return the update config
        return dict(arc_welder=arc_welder_info)
Ejemplo n.º 4
0
def on_identity_loaded(sender, identity):
    # If authenticated, you're an admin - yay!
    if not isinstance(identity, flask_principal.AnonymousIdentity):
        identity.provides.add(flask_principal.RoleNeed('admin'))
Ejemplo n.º 5
0
import flask
import flask_principal
import flask_saml

app = flask.Flask(__name__)
principals = flask_principal.Principal(app)
app.config.update({
    'SECRET_KEY': 'soverysecret',
    'SAML_METADATA_URL': 'https://metadata-url',
})
saml = flask_saml.FlaskSAML(app)

# Create a permission with a single Need, in this case a RoleNeed.
admin_permission = flask_principal.Permission(flask_principal.RoleNeed('admin'))

#
# Connect SAML & Principal
#

@flask_saml.saml_authenticated.connect_via(app)
def on_saml_authenticated(sender, subject, attributes, auth):
    # We have a logged in user, inform Flask-Principal
    flask_principal.identity_changed.send(
        flask.current_app._get_current_object(),
        identity=get_identity(),
    )


@flask_saml.saml_log_out.connect_via(app)
def on_saml_logout(sender):
    # Let Flask-Principal know the user is gone
Ejemplo n.º 6
0
class ArcWelderPlugin(
    octoprint.plugin.StartupPlugin,
    octoprint.plugin.TemplatePlugin,
    octoprint.plugin.SettingsPlugin,
    octoprint.plugin.AssetPlugin,
    octoprint.plugin.BlueprintPlugin,
    octoprint.plugin.EventHandlerPlugin
):

    if LooseVersion(octoprint.server.VERSION) >= LooseVersion("1.4"):
        import octoprint.access.permissions as permissions

        admin_permission = permissions.Permissions.ADMIN
    else:
        import flask_principal

        admin_permission = flask_principal.Permission(flask_principal.RoleNeed("admin"))

    FILE_PROCESSING_BOTH = "both"
    FILE_PROCESSING_AUTO = "auto-only"
    FILE_PROCESSING_MANUAL = "manual-only"

    def __init__(self):
        super(ArcWelderPlugin, self).__init__()
        self.preprocessing_job_guid = None
        self.preprocessing_job_source_file_path = ""
        self.preprocessing_job_target_file_name = ""
        self.is_cancelled = False
        self._processing_queue = queue.Queue()
        self.settings_default = dict(
            use_octoprint_settings=True,
            g90_g91_influences_extruder=False,
            resolution_mm=0.05,
            max_radius_mm=1000*1000,  # 1KM, pretty big :)
            overwrite_source_file=False,
            target_prefix="",
            target_postfix=".aw",
            notification_settings=dict(
                show_started_notification=True,
                show_progress_bar=True,
                show_completed_notification=True
            ),
            feature_settings=dict(
                file_processing=ArcWelderPlugin.FILE_PROCESSING_BOTH
            ),
            enabled=True,
            logging_configuration=dict(
                default_log_level=log.ERROR,
                log_to_console=False,
                enabled_loggers=[],
            ),
            version=__version__,
            git_version=__git_version__,
        )
        # start the preprocessor worker
        self._preprocessor_worker = None

    def on_after_startup(self):
        logging_configurator.configure_loggers(
            self._log_file_path, self._logging_configuration
        )
        self._preprocessor_worker = preprocessor.PreProcessorWorker(
            self.get_plugin_data_folder(),
            self._processing_queue,
            self._get_is_printing,
            self.save_preprocessed_file,
            self.preprocessing_started,
            self.preprocessing_progress,
            self.preprocessing_cancelled,
            self.preprocessing_failed,
            self.preprocessing_success,
            self.preprocessing_completed,
        )
        self._preprocessor_worker.daemon = True
        self._preprocessor_worker.start()
        logger.info("Startup Complete.")

    # Events
    def get_settings_defaults(self):
        # plugin_version is not instantiated when __init__ is called.  Update it now.
        self.settings_default["version"] = self._plugin_version
        return self.settings_default

    def on_settings_save(self, data):
        octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
        # reconfigure logging
        logging_configurator.configure_loggers(
            self._log_file_path, self._logging_configuration
        )

    def get_template_configs(self):
        return [
            dict(
                type="settings",
                custom_bindings=True,
                template="arc_welder_settings.jinja2",
            )
        ]

    # Blueprints
    @octoprint.plugin.BlueprintPlugin.route("/cancelPreprocessing", methods=["POST"])
    @restricted_access
    def cancel_preprocessing_request(self):
        with ArcWelderPlugin.admin_permission.require(http_exception=403):
            request_values = request.get_json()
            cancel_all = request_values["cancel_all"]
            preprocessing_job_guid = request_values["preprocessing_job_guid"]
            if cancel_all:
                self._preprocessor_worker.cancel_all()

            if self.preprocessing_job_guid is None or preprocessing_job_guid != str(
                self.preprocessing_job_guid
            ):
                # return without doing anything, this job is already over
                return jsonify({"success": True})

            logger.info("Cancelling Preprocessing for /cancelPreprocessing.")
            self.preprocessing_job_guid = None
            self.is_cancelled = True
            return jsonify({"success": True})

    @octoprint.plugin.BlueprintPlugin.route("/clearLog", methods=["POST"])
    @restricted_access
    def clear_log_request(self):
        with ArcWelderPlugin.admin_permission.require(http_exception=403):
            request_values = request.get_json()
            clear_all = request_values["clear_all"]
            if clear_all:
                logger.info("Clearing all log files.")
            else:
                logger.info("Rolling over most recent log.")

            logging_configurator.do_rollover(clear_all=clear_all)
            return jsonify({"success": True})

    # Preprocess from file sidebar
    @octoprint.plugin.BlueprintPlugin.route("/process", methods=["POST"])
    @restricted_access
    def process_request(self):
        with ArcWelderPlugin.admin_permission.require(http_exception=403):
            if self._enabled:
                request_values = request.get_json()
                path = request_values["path"]
                # decode the path
                path = urllibparse.unquote(path)
                self.add_file_to_preprocessor_queue(path)
                return jsonify({"success": True})
            return jsonify({"success": False, "message": "Arc Welder is Disabled."})

    @octoprint.plugin.BlueprintPlugin.route("/restoreDefaultSettings", methods=["POST"])
    @restricted_access
    def restore_default_settings_request(self):
        with ArcWelderPlugin.admin_permission.require(http_exception=403):
            self._settings.set([], self.settings_default)
            self._settings.save()
            return jsonify({"success": True})

    # Callback Handler for /downloadFile
    # uses the ArcWelderLargeResponseHandler
    def download_file_request(self, request_handler):
        download_file_path = None
        # get the args
        file_type = request_handler.get_query_arguments('type')[0]
        if file_type == 'log':
            full_path = self._log_file_path
        if full_path is None or not os.path.isfile(full_path):
            raise tornado.web.HTTPError(404)
        return full_path

    def send_notification_toast(
        self, toast_type, title, message, auto_hide, key=None, close_keys=[]
    ):
        data = {
            "message_type": "toast",
            "toast_type": toast_type,
            "title": title,
            "message": message,
            "auto_hide": auto_hide,
            "key": key,
            "close_keys": close_keys,
        }
        self._plugin_manager.send_plugin_message(self._identifier, data)
        # return true to continue, false to terminate

    # ~~ AssetPlugin mixin
    def get_assets(self):
        # Define your plugin's asset files to automatically include in the
        # core UI here.
        return dict(
            js=[
                "js/showdown.min.js",
                "js/pnotify_extensions.js",
                "js/markdown_help.js",
                "js/arc_welder.js",
                "js/arc_welder.settings.js",
            ],
            css=["css/arc_welder.css"],
        )

    def _get_is_printing(self):
        return self._printer.is_printing()
    # Properties

    @property
    def _log_file_path(self):
        return self._settings.get_plugin_logfile_path()

    # Settings Properties
    @property
    def _logging_configuration(self):
        logging_configurator = self._settings.get(["logging_configuration"])
        if logging_configurator is None:
            logging_configurator = self.settings_default["logging_configuration"]
        return logging_configurator

    @property
    def _gcode_conversion_log_level(self):
        if "enabled_loggers" not in self._logging_configuration:
            return log.ERROR
        else:
            enabled_loggers = self._logging_configuration["enabled_loggers"]
        log_level = log.CRITICAL
        for logger in enabled_loggers:
            if logger["name"] == "arc_welder.gcode_conversion":
                log_level = logger["log_level"]
                break
        return log_level

    @property
    def _enabled(self):
        enabled = self._settings.get_boolean(["enabled"])
        if enabled is None:
            enabled = self.settings_default["enabled"]
        return enabled

    @property
    def _auto_pre_processing_enabled(self):
        return self._settings.get(["feature_settings", "file_processing"]) in [
            ArcWelderPlugin.FILE_PROCESSING_BOTH, ArcWelderPlugin.FILE_PROCESSING_AUTO
        ]

    @property
    def _use_octoprint_settings(self):
        use_octoprint_settings = self._settings.get_boolean(["use_octoprint_settings"])
        if use_octoprint_settings is None:
            use_octoprint_settings = self.settings_default["use_octoprint_settings"]
        return use_octoprint_settings

    @property
    def _g90_g91_influences_extruder(self):
        if self._use_octoprint_settings:
            g90_g91_influences_extruder = self._settings.global_get(
                ["feature", "g90InfluencesExtruder"]
            )
        else:
            g90_g91_influences_extruder = self._settings.get_boolean(
                ["g90_g91_influences_extruder"]
            )
        if g90_g91_influences_extruder is None:
            g90_g91_influences_extruder = self.settings_default[
                "g90_g91_influences_extruder"
            ]
        return g90_g91_influences_extruder

    @property
    def _resolution_mm(self):
        resolution_mm = self._settings.get_float(["resolution_mm"])
        if resolution_mm is None:
            resolution_mm = self.settings_default["resolution_mm"]
        return resolution_mm

    @property
    def _max_radius_mm(self):
        max_radius_mm = self._settings.get_float(["max_radius_mm"])
        if max_radius_mm is None:
            max_radius_mm = self.settings_default["max_radius_mm"]
        return max_radius_mm

    @property
    def _overwrite_source_file(self):
        overwrite_source_file = self._settings.get_boolean(["overwrite_source_file"])
        if overwrite_source_file is None:
            overwrite_source_file = self.settings_default["overwrite_source_file"]
        return overwrite_source_file

    @property
    def _target_prefix(self):
        target_prefix = self._settings.get(["target_prefix"])
        if target_prefix is None:
            target_prefix = self.settings_default["target_prefix"]
        else:
            target_prefix = target_prefix.strip()
        return target_prefix

    @property
    def _target_postfix(self):
        target_postfix = self._settings.get(["target_postfix"])
        if target_postfix is None:
            target_postfix = self.settings_default["target_postfix"]
        else:
            target_postfix = target_postfix.strip()
        return target_postfix

    @property
    def _show_started_notification(self):
        return self._settings.get(["notification_settings", "show_started_notification"])

    @property
    def _show_progress_bar(self):
        return self._settings.get(["notification_settings", "show_progress_bar"])

    @property
    def _show_completed_notification(self):
        return self._settings.get(["notification_settings", "show_completed_notification"])

    def get_storage_path_and_name(self, storage_path, add_prefix_and_postfix):
        path, name = self._file_manager.split_path(FileDestinations.LOCAL, storage_path)
        if add_prefix_and_postfix:
            file_name = utilities.remove_extension_from_filename(name)
            file_extension = utilities.get_extension_from_filename(name)
            new_name = "{0}{1}{2}.{3}".format(self._target_prefix, file_name, self._target_postfix, file_extension)
        else:
            new_name = name
        new_path = self._file_manager.join_path(FileDestinations.LOCAL, path, new_name)
        return new_path, new_name

    def get_preprocessor_arguments(self, source_path_on_disk):
        return {
            "path": source_path_on_disk,
            "resolution_mm": self._resolution_mm,
            "max_radius_mm": self._max_radius_mm,
            "g90_g91_influences_extruder": self._g90_g91_influences_extruder,
            "log_level": self._gcode_conversion_log_level
        }

    def save_preprocessed_file(self, path, preprocessor_args):
        # get the file name and path
        new_path, new_name = self.get_storage_path_and_name(
            path, not self._overwrite_source_file
        )
        if self._overwrite_source_file:
            logger.info("Overwriting source file at %s with the processed file.", path)
        else:
            logger.info("Arc compression complete, creating a new gcode file: %s", new_name)


        # TODO:  Look through the analysis queue, and stop analysis if the file is being analyzed.  Perhaps we need
        # to wait?
        # Create the new file object
        new_file_object = octoprint.filemanager.util.DiskFileWrapper(
            new_name, preprocessor_args["target_file_path"], move=True
        )

        self._file_manager.add_file(
            FileDestinations.LOCAL,
            new_path,
            new_file_object,
            allow_overwrite=True,
            display=new_name,
        )
        self._file_manager.set_additional_metadata(
            FileDestinations.LOCAL, new_path, "arc_welder", True, overwrite=True, merge=False
        )

    def preprocessing_started(self, path, preprocessor_args):
        new_path, new_name = self.get_storage_path_and_name(
            path, not self._overwrite_source_file
        )
        self.preprocessing_job_guid = str(uuid.uuid4())
        self.preprocessing_job_source_file_path = path
        self.preprocessing_job_target_file_name = new_name

        logger.info(
            "Starting pre-processing with the following arguments:"
            "\n\tsource_file_path: %s"
            "\n\tresolution_mm: %.3f"
            "\n\tg90_g91_influences_extruder: %r"
            "\n\tlog_level: %d",
            preprocessor_args["path"],
            preprocessor_args["resolution_mm"],
            preprocessor_args["g90_g91_influences_extruder"],
            preprocessor_args["log_level"]
        )

        if self._show_started_notification:
            # A bit of a hack.  Need to rethink the start notification.
            if self._show_progress_bar:
                data = {
                    "message_type": "preprocessing-start",
                    "source_filename": self.preprocessing_job_source_file_path,
                    "target_filename": self.preprocessing_job_target_file_name,
                    "preprocessing_job_guid": self.preprocessing_job_guid
                }
                self._plugin_manager.send_plugin_message(self._identifier, data)
            else:
                message = "Arc Welder is processing '{0}'.  Please wait...".format(
                    self.preprocessing_job_source_file_path
                )
                self.send_notification_toast(
                    "info", "Pre-Processing Gcode", message, True, "preprocessing_start", ["preprocessing_start"]
                )

    def preprocessing_progress(self, progress):
        if self._show_progress_bar:
            data = {
                "message_type": "preprocessing-progress",
                "percent_complete": progress["percent_complete"],
                "seconds_elapsed": progress["seconds_elapsed"],
                "seconds_remaining": progress["seconds_remaining"],
                "gcodes_processed": progress["gcodes_processed"],
                "lines_processed": progress["lines_processed"],
                "points_compressed": progress["points_compressed"],
                "arcs_created": progress["arcs_created"],
                "source_file_size": progress["source_file_size"],
                "source_file_position": progress["source_file_position"],
                "target_file_size": progress["target_file_size"],
                "compression_ratio": progress["compression_ratio"],
                "compression_percent": progress["compression_percent"],
                "source_filename": self.preprocessing_job_source_file_path,
                "target_filename": self.preprocessing_job_target_file_name,
                "preprocessing_job_guid": self.preprocessing_job_guid
            }
            self._plugin_manager.send_plugin_message(self._identifier, data)
            time.sleep(0.01)
        # return true if processing should continue.  This is set by the cancelled blueprint plugin.
        return not self.is_cancelled

    def preprocessing_cancelled(self, path, preprocessor_args):
        message = "Preprocessing has been cancelled for '{0}'.".format(path)
        data = {
            "message_type": "preprocessing-cancelled",
            "source_filename": self.preprocessing_job_source_file_path,
            "target_filename": self.preprocessing_job_target_file_name,
            "preprocessing_job_guid": self.preprocessing_job_guid,
            "message": message
        }
        self._plugin_manager.send_plugin_message(self._identifier, data)

    def preprocessing_success(self, results, path, preprocessor_args):
        # save the newly created file.  This must be done before
        # exiting this callback because the target file isn't
        # guaranteed to exist later.
        self.save_preprocessed_file(path, preprocessor_args)
        if self._show_completed_notification:
            data = {
                "message_type": "preprocessing-success",
                "results": results,
                "source_filename": self.preprocessing_job_source_file_path,
                "target_filename": self.preprocessing_job_target_file_name,
                "preprocessing_job_guid": self.preprocessing_job_guid,
            }
            self._plugin_manager.send_plugin_message(self._identifier, data)

    def preprocessing_completed(self):
        data = {
            "message_type": "preprocessing-complete"
        }
        self.preprocessing_job_guid = None
        self.preprocessing_job_source_file_path = None
        self.preprocessing_job_target_file_name = None
        self._plugin_manager.send_plugin_message(self._identifier, data)

    def preprocessing_failed(self, message):
        data = {
            "message_type": "preprocessing-failed",
            "source_filename": self.preprocessing_job_source_file_path,
            "target_filename": self.preprocessing_job_target_file_name,
            "preprocessing_job_guid": self.preprocessing_job_guid,
            "message": message
        }
        self._plugin_manager.send_plugin_message(self._identifier, data)

    def on_event(self, event, payload):
        if not self._enabled or not self._auto_pre_processing_enabled:
            return

        if event == Events.UPLOAD:
            #storage = payload["storage"]
            path = payload["path"]
            name = payload["name"]
            target = payload["target"]

            if path == self.preprocessing_job_source_file_path or name == self.preprocessing_job_target_file_name:
                return

            if not octoprint.filemanager.valid_file_type(
                    path, type="gcode"
            ):
                return

            if not target == FileDestinations.LOCAL:
                return

            metadata = self._file_manager.get_metadata(target, path)
            if "arc_welder" in metadata:
                return

            self.add_file_to_preprocessor_queue(path)

    def add_file_to_preprocessor_queue(self, path):
        # get the file by path
        # file = self._file_manager.get_file(FileDestinations.LOCAL, path)
        if self._printer.is_printing():
            self.send_notification_toast(
                "error", "Arc-Welder: Unable to Process",
                "Cannot preprocess gcode while a print is in progress because print quality may be affected.  The "
                "gcode will be processed as soon as the print has completed.",
                False,
                key="unable_to_process", close_keys=["unable_to_process"]
            )
            return

        logger.info("Received a new gcode file for processing.  FileName: %s.", path)

        self.is_cancelled = False
        path_on_disk = self._file_manager.path_on_disk(FileDestinations.LOCAL, path)
        preprocessor_args = self.get_preprocessor_arguments(path_on_disk)
        self._processing_queue.put((path, preprocessor_args))

    def register_custom_routes(self, server_routes, *args, **kwargs):
        # version specific permission validator
        if LooseVersion(octoprint.server.VERSION) >= LooseVersion("1.4"):
            admin_validation_chain = [
                util.tornado.access_validation_factory(app, util.flask.admin_validator),
            ]
        else:
            # the concept of granular permissions does not exist in this version of Octoprint.  Fallback to the
            # admin role
            def admin_permission_validator(flask_request):
                user = util.flask.get_flask_user_from_request(flask_request)
                if user is None or not user.is_authenticated() or not user.is_admin():
                    raise tornado.web.HTTPError(403)
            permission_validator = admin_permission_validator
            admin_validation_chain = [util.tornado.access_validation_factory(app, permission_validator), ]
        return [
            (
                r"/downloadFile",
                ArcWelderLargeResponseHandler,
                dict(
                    request_callback=self.download_file_request,
                    as_attachment=True,
                    access_validation=util.tornado.validation_chain(*admin_validation_chain)
                )

            )
        ]

        # ~~ software update hook

    arc_welder_update_info = dict(
        displayName="Arc Welder: Anti-Stutter",
        # version check: github repository
        type="github_release",
        user="******",
        repo="ArcWelderPlugin",
        pip="https://github.com/FormerLurker/ArcWelderPlugin/archive/{target_version}.zip",
        stable_branch=dict(branch="master", commitish=["master"], name="Stable"),
        release_compare='custom',
        prerelease_branches=[
            dict(
                branch="rc/maintenance",
                commitish=["master", "rc/maintenance"],  # maintenance RCs (include master)
                name="Maintenance RCs"
            ),
            dict(
                branch="rc/devel",
                commitish=["master", "rc/maintenance", "rc/devel"],  # devel & maintenance RCs (include master)
                name="Devel RCs"
            )
        ],
    )

    def get_release_info(self):
        # get the checkout type from the software updater
        prerelease_channel = None
        is_prerelease = False
        # get this for reference.  Eventually I'll have to use it!
        # is the software update set to prerelease?

        if self._settings.global_get(["plugins", "softwareupdate", "checks", "octoprint", "prerelease"]):
            # If it's a prerelease, look at the channel and configure the proper branch for Arc Welder
            prerelease_channel = self._settings.global_get(
                ["plugins", "softwareupdate", "checks", "octoprint", "prerelease_channel"]
            )
            if prerelease_channel == "rc/maintenance":
                is_prerelease = True
                prerelease_channel = "rc/maintenance"
            elif prerelease_channel == "rc/devel":
                is_prerelease = True
                prerelease_channel = "rc/devel"
        ArcWelderPlugin.arc_welder_update_info["displayVersion"] = self._plugin_version
        ArcWelderPlugin.arc_welder_update_info["current"] = self._plugin_version
        ArcWelderPlugin.arc_welder_update_info["prerelease"] = is_prerelease
        if prerelease_channel is not None:
            ArcWelderPlugin.arc_welder_update_info["prerelease_channel"] = prerelease_channel

        return dict(
            arc_welder=ArcWelderPlugin.arc_welder_update_info
        )

    def get_update_information(self):
        # moved most of the heavy lifting to get_latest, since I need to do a custom version compare.
        # AND I want to use the most recent software update release channel settings.
        return self.get_release_info()
Ejemplo n.º 7
0
import mydojo.const


LOGIN_MANAGER = flask_login.LoginManager()
PRINCIPAL     = flask_principal.Principal(skip_static = True)


MembershipNeed = partial(flask_principal.Need, 'membership')   # pylint: disable=locally-disabled,invalid-name
MembershipNeed.__doc__ = """A need with the method preset to `"membership"`."""

ManagementNeed = partial(flask_principal.Need, 'management')   # pylint: disable=locally-disabled,invalid-name
ManagementNeed.__doc__ = """A need with the method preset to `"management"`."""


PERMISSION_ADMIN = flask_principal.Permission(
    flask_principal.RoleNeed(mydojo.const.ROLE_ADMIN)
)
"""
The :py:class:`flask_principal.Permission` permission for users with *admin* role
(ultimate power-user with unrestricted access to the whole system).
"""

PERMISSION_DEVELOPER = flask_principal.Permission(
    flask_principal.RoleNeed(mydojo.const.ROLE_DEVELOPER)
)
"""
The :py:class:`flask_principal.Permission` permission for users with *developer* role
(system developers with access to additional development and debugging data output).
"""

PERMISSION_USER = flask_principal.Permission(
Ejemplo n.º 8
0
class ArcWelderPlugin(octoprint.plugin.StartupPlugin,
                      octoprint.plugin.TemplatePlugin,
                      octoprint.plugin.SettingsPlugin,
                      octoprint.plugin.AssetPlugin,
                      octoprint.plugin.BlueprintPlugin,
                      octoprint.plugin.EventHandlerPlugin):

    if LooseVersion(octoprint.server.VERSION) >= LooseVersion("1.4"):
        import octoprint.access.permissions as permissions

        admin_permission = permissions.Permissions.ADMIN
    else:
        import flask_principal

        admin_permission = flask_principal.Permission(
            flask_principal.RoleNeed("admin"))

    FILE_PROCESSING_BOTH = "both"
    FILE_PROCESSING_AUTO = "auto-only"
    FILE_PROCESSING_MANUAL = "manual-only"

    SOURCE_FILE_DELETE_BOTH = "both"
    SOURCE_FILE_DELETE_AUTO = "auto-only"
    SOURCE_FILE_DELETE_MANUAL = "manual-only"
    SOURCE_FILE_DELETE_DISABLED = "disabled"

    def __init__(self):
        super(ArcWelderPlugin, self).__init__()
        self.preprocessing_job_guid = None
        self.preprocessing_job_source_file_path = ""
        self.preprocessing_job_target_file_name = ""
        self.is_cancelled = False
        self._processing_queue = queue.Queue()
        self.settings_default = dict(
            use_octoprint_settings=True,
            g90_g91_influences_extruder=False,
            resolution_mm=0.05,
            max_radius_mm=1000 * 1000,  # 1KM, pretty big :)
            overwrite_source_file=False,
            target_prefix="",
            target_postfix=".aw",
            notification_settings=dict(show_started_notification=True,
                                       show_progress_bar=True,
                                       show_completed_notification=True),
            feature_settings=dict(
                file_processing=ArcWelderPlugin.FILE_PROCESSING_BOTH,
                delete_source=ArcWelderPlugin.SOURCE_FILE_DELETE_DISABLED),
            enabled=True,
            logging_configuration=dict(
                default_log_level=log.ERROR,
                log_to_console=False,
                enabled_loggers=[],
            ),
            version=__version__,
            git_version=__git_version__,
        )
        # start the preprocessor worker
        self._preprocessor_worker = None

    def on_after_startup(self):
        logging_configurator.configure_loggers(self._log_file_path,
                                               self._logging_configuration)
        self._preprocessor_worker = preprocessor.PreProcessorWorker(
            self.get_plugin_data_folder(),
            self._processing_queue,
            self._get_is_printing,
            self.preprocessing_started,
            self.preprocessing_progress,
            self.preprocessing_cancelled,
            self.preprocessing_failed,
            self.preprocessing_success,
            self.preprocessing_completed,
        )
        self._preprocessor_worker.daemon = True
        self._preprocessor_worker.start()
        logger.info("Startup Complete.")

    # Events
    def get_settings_defaults(self):
        # plugin_version is not instantiated when __init__ is called.  Update it now.
        self.settings_default["version"] = self._plugin_version
        return self.settings_default

    def on_settings_save(self, data):
        octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
        # reconfigure logging
        logging_configurator.configure_loggers(self._log_file_path,
                                               self._logging_configuration)

    def get_template_configs(self):
        return [
            dict(
                type="settings",
                custom_bindings=True,
                template="arc_welder_settings.jinja2",
            )
        ]

    # Blueprints
    @octoprint.plugin.BlueprintPlugin.route("/cancelPreprocessing",
                                            methods=["POST"])
    @restricted_access
    def cancel_preprocessing_request(self):
        with ArcWelderPlugin.admin_permission.require(http_exception=403):
            request_values = request.get_json()
            cancel_all = request_values["cancel_all"]
            preprocessing_job_guid = request_values["preprocessing_job_guid"]
            if cancel_all:
                self._preprocessor_worker.cancel_all()

            if self.preprocessing_job_guid is None or preprocessing_job_guid != str(
                    self.preprocessing_job_guid):
                # return without doing anything, this job is already over
                return jsonify({"success": True})

            logger.info("Cancelling Preprocessing for /cancelPreprocessing.")
            self.preprocessing_job_guid = None
            self.is_cancelled = True
            return jsonify({"success": True})

    @octoprint.plugin.BlueprintPlugin.route("/clearLog", methods=["POST"])
    @restricted_access
    def clear_log_request(self):
        with ArcWelderPlugin.admin_permission.require(http_exception=403):
            request_values = request.get_json()
            clear_all = request_values["clear_all"]
            if clear_all:
                logger.info("Clearing all log files.")
            else:
                logger.info("Rolling over most recent log.")

            logging_configurator.do_rollover(clear_all=clear_all)
            return jsonify({"success": True})

    # Preprocess from file sidebar
    @octoprint.plugin.BlueprintPlugin.route("/process", methods=["POST"])
    @restricted_access
    def process_request(self):
        with ArcWelderPlugin.admin_permission.require(http_exception=403):
            if self._enabled:
                request_values = request.get_json()
                path = request_values["path"]
                origin = request_values["origin"]
                # decode the path
                path = urllibparse.unquote(path)
                # get the metadata for the file
                metadata = self._file_manager.get_metadata(origin, path)
                if "arc_welder" not in metadata:
                    # Extract only the supported metadata from the added file
                    additional_metadata = self.get_additional_metadata(
                        metadata)
                    # add the file and metadata to the processor queue
                    self.add_file_to_preprocessor_queue(
                        path, additional_metadata, True)
                    return jsonify({"success": True})
            return jsonify({
                "success": False,
                "message": "Arc Welder is Disabled."
            })

    @octoprint.plugin.BlueprintPlugin.route("/restoreDefaultSettings",
                                            methods=["POST"])
    @restricted_access
    def restore_default_settings_request(self):
        with ArcWelderPlugin.admin_permission.require(http_exception=403):
            self._settings.set([], self.settings_default)
            # force save the settings and trigger a SettingsUpdated event
            self._settings.save(trigger_event=True)
            return jsonify({"success": True})

    # Callback Handler for /downloadFile
    # uses the ArcWelderLargeResponseHandler
    def download_file_request(self, request_handler):
        download_file_path = None
        # get the args
        file_type = request_handler.get_query_arguments('type')[0]
        if file_type == 'log':
            full_path = self._log_file_path
        if full_path is None or not os.path.isfile(full_path):
            raise tornado.web.HTTPError(404)
        return full_path

    def send_notification_toast(self,
                                toast_type,
                                title,
                                message,
                                auto_hide,
                                key=None,
                                close_keys=[]):
        data = {
            "message_type": "toast",
            "toast_type": toast_type,
            "title": title,
            "message": message,
            "auto_hide": auto_hide,
            "key": key,
            "close_keys": close_keys,
        }
        self._plugin_manager.send_plugin_message(self._identifier, data)

    # ~~ AssetPlugin mixin
    def get_assets(self):
        # Define your plugin's asset files to automatically include in the
        # core UI here.
        return dict(
            js=[
                "js/showdown.min.js",
                "js/pnotify_extensions.js",
                "js/markdown_help.js",
                "js/arc_welder.js",
                "js/arc_welder.settings.js",
            ],
            css=["css/arc_welder.css"],
        )

    def _is_file_selected(self, path, origin):
        current_job = self._printer.get_current_job()
        current_file = current_job.get("file", {'path': "", "origin": ""})
        current_file_path = current_file["path"]
        # ensure the current file path starts with a /
        if current_file_path and current_file_path[0] != '/':
            current_file_path = '/' + current_file_path
        current_file_origin = current_file["origin"]
        return path == current_file_path and origin == current_file_origin

    def _get_is_printing(self, path=None):
        # If the printer is NOT printing, always return false
        if not self._printer.is_printing():
            return False
        # If the path parameter is provided, check for a locally printing file of the same path
        if path:
            return self._is_file_selected(path, FileDestinations.LOCAL)

        return False

    # Properties

    @property
    def _log_file_path(self):
        return self._settings.get_plugin_logfile_path()

    # Settings Properties
    @property
    def _logging_configuration(self):
        logging_configurator = self._settings.get(["logging_configuration"])
        if logging_configurator is None:
            logging_configurator = self.settings_default[
                "logging_configuration"]
        return logging_configurator

    @property
    def _gcode_conversion_log_level(self):
        if "enabled_loggers" not in self._logging_configuration:
            return log.ERROR
        else:
            enabled_loggers = self._logging_configuration["enabled_loggers"]
        log_level = log.CRITICAL
        for logger in enabled_loggers:
            if logger["name"] == "arc_welder.gcode_conversion":
                log_level = logger["log_level"]
                break
        return log_level

    @property
    def _enabled(self):
        enabled = self._settings.get_boolean(["enabled"])
        if enabled is None:
            enabled = self.settings_default["enabled"]
        return enabled

    @property
    def _delete_source_after_manual_processing(self):
        return self._settings.get(["feature_settings", "delete_source"]) in [
            ArcWelderPlugin.SOURCE_FILE_DELETE_BOTH,
            ArcWelderPlugin.SOURCE_FILE_DELETE_MANUAL
        ] and not self._overwrite_source_file

    @property
    def _delete_source_after_automatic_processing(self):
        return self._settings.get(["feature_settings", "delete_source"]) in [
            ArcWelderPlugin.SOURCE_FILE_DELETE_BOTH,
            ArcWelderPlugin.SOURCE_FILE_DELETE_AUTO
        ] and not self._overwrite_source_file

    @property
    def _auto_pre_processing_enabled(self):
        return self._settings.get(["feature_settings", "file_processing"]) in [
            ArcWelderPlugin.FILE_PROCESSING_BOTH,
            ArcWelderPlugin.FILE_PROCESSING_AUTO
        ]

    @property
    def _use_octoprint_settings(self):
        use_octoprint_settings = self._settings.get_boolean(
            ["use_octoprint_settings"])
        if use_octoprint_settings is None:
            use_octoprint_settings = self.settings_default[
                "use_octoprint_settings"]
        return use_octoprint_settings

    @property
    def _g90_g91_influences_extruder(self):
        if self._use_octoprint_settings:
            g90_g91_influences_extruder = self._settings.global_get(
                ["feature", "g90InfluencesExtruder"])
        else:
            g90_g91_influences_extruder = self._settings.get_boolean(
                ["g90_g91_influences_extruder"])
        if g90_g91_influences_extruder is None:
            g90_g91_influences_extruder = self.settings_default[
                "g90_g91_influences_extruder"]
        return g90_g91_influences_extruder

    @property
    def _resolution_mm(self):
        resolution_mm = self._settings.get_float(["resolution_mm"])
        if resolution_mm is None:
            resolution_mm = self.settings_default["resolution_mm"]
        return resolution_mm

    @property
    def _max_radius_mm(self):
        max_radius_mm = self._settings.get_float(["max_radius_mm"])
        if max_radius_mm is None:
            max_radius_mm = self.settings_default["max_radius_mm"]
        return max_radius_mm

    @property
    def _overwrite_source_file(self):
        overwrite_source_file = self._settings.get_boolean(
            ["overwrite_source_file"])
        if overwrite_source_file is None:
            overwrite_source_file = self.settings_default[
                "overwrite_source_file"]
        return overwrite_source_file

    @property
    def _target_prefix(self):
        target_prefix = self._settings.get(["target_prefix"])
        if target_prefix is None:
            target_prefix = self.settings_default["target_prefix"]
        else:
            target_prefix = target_prefix.strip()
        return target_prefix

    @property
    def _target_postfix(self):
        target_postfix = self._settings.get(["target_postfix"])
        if target_postfix is None:
            target_postfix = self.settings_default["target_postfix"]
        else:
            target_postfix = target_postfix.strip()
        return target_postfix

    @property
    def _show_started_notification(self):
        return self._settings.get(
            ["notification_settings", "show_started_notification"])

    @property
    def _show_progress_bar(self):
        return self._settings.get(
            ["notification_settings", "show_progress_bar"])

    @property
    def _show_completed_notification(self):
        return self._settings.get(
            ["notification_settings", "show_completed_notification"])

    @property
    def _delete_source_after_processing(self):
        return self._settings.get(["delete_source_after_processing"])

    def get_storage_path_and_name(self, storage_path, add_prefix_and_postfix):
        path, name = self._file_manager.split_path(FileDestinations.LOCAL,
                                                   storage_path)
        if add_prefix_and_postfix:
            file_name = utilities.remove_extension_from_filename(name)
            file_extension = utilities.get_extension_from_filename(name)
            new_name = "{0}{1}{2}.{3}".format(self._target_prefix, file_name,
                                              self._target_postfix,
                                              file_extension)
        else:
            new_name = name
        new_path = self._file_manager.join_path(FileDestinations.LOCAL, path,
                                                new_name)
        return new_path, new_name

    def get_preprocessor_arguments(self, source_path_on_disk):
        return {
            "path": source_path_on_disk,
            "resolution_mm": self._resolution_mm,
            "max_radius_mm": self._max_radius_mm,
            "g90_g91_influences_extruder": self._g90_g91_influences_extruder,
            "log_level": self._gcode_conversion_log_level
        }

    def save_preprocessed_file(self, path, preprocessor_args, results,
                               additional_metadata):
        # get the file name and path
        new_path, new_name = self.get_storage_path_and_name(
            path, not self._overwrite_source_file)

        if self._get_is_printing(new_path):
            raise TargetFileSaveError(
                "The source file will be overwritten, but it is currently printing, cannot overwrite."
            )

        if self._overwrite_source_file:
            logger.info(
                "Overwriting source file at %s with the processed file.", path)
        else:
            logger.info(
                "Arc compression complete, creating a new gcode file: %s",
                new_name)

        new_file_object = octoprint.filemanager.util.DiskFileWrapper(
            new_name, preprocessor_args["target_file_path"], move=True)

        self._file_manager.add_file(
            FileDestinations.LOCAL,
            new_path,
            new_file_object,
            allow_overwrite=True,
            display=new_name,
        )
        self._file_manager.set_additional_metadata(FileDestinations.LOCAL,
                                                   new_path,
                                                   "arc_welder",
                                                   True,
                                                   overwrite=True,
                                                   merge=False)
        progress = results["progress"]
        metadata = {
            "source_file_total_length": progress["source_file_total_length"],
            "target_file_total_length": progress["target_file_total_length"],
            "source_file_total_count": progress["source_file_total_count"],
            "target_file_total_count": progress["target_file_total_count"],
            "segment_statistics_text": progress["segment_statistics_text"],
            "seconds_elapsed": progress["seconds_elapsed"],
            "gcodes_processed": progress["gcodes_processed"],
            "lines_processed": progress["lines_processed"],
            "points_compressed": progress["points_compressed"],
            "arcs_created": progress["arcs_created"],
            "source_file_size": progress["source_file_size"],
            "source_file_position": progress["source_file_position"],
            "target_file_size": progress["target_file_size"],
            "compression_ratio": progress["compression_ratio"],
            "compression_percent": progress["compression_percent"],
            "source_filename": results["source_filename"],
            "target_filename": new_name,
            "preprocessing_job_guid": self.preprocessing_job_guid
        }

        self._file_manager.set_additional_metadata(FileDestinations.LOCAL,
                                                   new_path,
                                                   "arc_welder_statistics",
                                                   metadata,
                                                   overwrite=True,
                                                   merge=False)

        # Add compatibility for ultimaker thumbnail package
        has_ultimaker_format_package_thumbnail = (
            "thumbnail" in additional_metadata
            and isinstance(additional_metadata['thumbnail'], string_types)
            and additional_metadata['thumbnail'].startswith(
                'plugin/UltimakerFormatPackage/thumbnail/'))
        # Add compatibility for PrusaSlicer thumbnail package
        has_prusa_slicer_thumbnail = (
            "thumbnail" in additional_metadata
            and isinstance(additional_metadata['thumbnail'], string_types)
            and additional_metadata['thumbnail'].startswith(
                'plugin/prusaslicerthumbnails/thumbnail/'))

        # delete the thumbnail src element if it exists, we will add it later if necessary
        if "thumbnail_src" in additional_metadata:
            del additional_metadata["thumbnail_src"]

        if has_ultimaker_format_package_thumbnail and not "thumbnail_src" in additional_metadata:
            additional_metadata["thumbnail_src"] = "UltimakerFormatPackage"
        elif has_prusa_slicer_thumbnail and not "thumbnail_src" in additional_metadata:
            additional_metadata["thumbnail_src"] = "prusaslicerthumbnails"

        # add the additional metadata
        if "thumbnail" in additional_metadata:
            current_path = additional_metadata["thumbnail"]
            thumbnail_src = None
            thumbnail = None
            if has_ultimaker_format_package_thumbnail:
                thumbnail_src = "UltimakerFormatPackage"
            elif has_prusa_slicer_thumbnail:
                thumbnail_src = "prusaslicerthumbnails"

            if thumbnail_src is not None:
                thumbnail = self.copy_thumbnail(thumbnail_src, current_path,
                                                new_name)
            # set the thumbnail path and src.  It will not be copied to the final metadata if the value is none
            additional_metadata["thumbnail"] = thumbnail
            additional_metadata["thumbnail_src"] = thumbnail_src

        # add all the metadata items
        for key, value in additional_metadata.items():
            if value is not None:
                self._file_manager.set_additional_metadata(
                    FileDestinations.LOCAL,
                    new_path,
                    key,
                    value,
                    overwrite=True,
                    merge=False)

        return new_path, new_name, metadata

    def copy_thumbnail(self, thumbnail_src, thumbnail_path, gcode_filename):
        # get the plugin implementation
        plugin_implementation = self._plugin_manager.get_plugin_info(
            thumbnail_src, True)
        if plugin_implementation:
            thumbnail_uri_root = 'plugin/' + thumbnail_src + '/thumbnail/'
            data_folder = plugin_implementation.implementation.get_plugin_data_folder(
            )
            # extract the file name from the path
            path = thumbnail_path.replace(thumbnail_uri_root, '')
            querystring_index = path.rfind('?')
            if querystring_index > -1:
                path = path[0:querystring_index]

            path = os.path.join(data_folder, path)
            # see if the thumbnail exists
            if os.path.isfile(path):
                # create a new path
                pre, ext = os.path.splitext(gcode_filename)
                new_thumb_name = pre + ".png"
                new_path = os.path.join(data_folder, new_thumb_name)
                new_metadata = (
                    thumbnail_uri_root + new_thumb_name + "?" +
                    "{:%Y%m%d%H%M%S}".format(datetime.datetime.now()))
                if path != new_path:
                    try:
                        copyfile(path, new_path)
                    except (IOError, OSError) as e:
                        logger.exception(
                            "An error occurred copying thumbnail from '%s' to '%s'",
                            path, new_path)
                        new_metadata = None
                return new_metadata
        return None

    def preprocessing_started(self, path, preprocessor_args):
        new_path, new_name = self.get_storage_path_and_name(
            path, not self._overwrite_source_file)
        self.preprocessing_job_guid = str(uuid.uuid4())
        self.preprocessing_job_source_file_path = path
        self.preprocessing_job_target_file_name = new_name
        self.is_cancelled = False

        logger.info(
            "Starting pre-processing with the following arguments:"
            "\n\tsource_file_path: %s"
            "\n\tresolution_mm: %.3f"
            "\n\tg90_g91_influences_extruder: %r"
            "\n\tlog_level: %d", preprocessor_args["path"],
            preprocessor_args["resolution_mm"],
            preprocessor_args["g90_g91_influences_extruder"],
            preprocessor_args["log_level"])

        if self._show_started_notification:
            # A bit of a hack.  Need to rethink the start notification.
            if self._show_progress_bar:
                data = {
                    "message_type": "preprocessing-start",
                    "source_filename": self.preprocessing_job_source_file_path,
                    "target_filename": self.preprocessing_job_target_file_name,
                    "preprocessing_job_guid": self.preprocessing_job_guid
                }
                self._plugin_manager.send_plugin_message(
                    self._identifier, data)
            else:
                message = "Arc Welder is processing '{0}'.  Please wait...".format(
                    self.preprocessing_job_source_file_path)
                self.send_notification_toast("info", "Pre-Processing Gcode",
                                             message, True,
                                             "preprocessing_start",
                                             ["preprocessing_start"])

    def preprocessing_progress(self, progress):
        if self._show_progress_bar:
            data = {
                "message_type": "preprocessing-progress",
                "percent_complete": progress["percent_complete"],
                "seconds_elapsed": progress["seconds_elapsed"],
                "seconds_remaining": progress["seconds_remaining"],
                "gcodes_processed": progress["gcodes_processed"],
                "lines_processed": progress["lines_processed"],
                "points_compressed": progress["points_compressed"],
                "arcs_created": progress["arcs_created"],
                "source_file_size": progress["source_file_size"],
                "source_file_position": progress["source_file_position"],
                "target_file_size": progress["target_file_size"],
                "compression_ratio": progress["compression_ratio"],
                "compression_percent": progress["compression_percent"],
                "source_filename": self.preprocessing_job_source_file_path,
                "target_filename": self.preprocessing_job_target_file_name,
                "preprocessing_job_guid": self.preprocessing_job_guid
            }
            self._plugin_manager.send_plugin_message(self._identifier, data)
            time.sleep(0.01)
        # return true if processing should continue.  This is set by the cancelled blueprint plugin.
        return not self.is_cancelled

    def preprocessing_cancelled(self, path, preprocessor_args):
        message = "Preprocessing has been cancelled for '{0}'.".format(path)
        data = {
            "message_type": "preprocessing-cancelled",
            "source_filename": self.preprocessing_job_source_file_path,
            "target_filename": self.preprocessing_job_target_file_name,
            "preprocessing_job_guid": self.preprocessing_job_guid,
            "message": message
        }
        self._plugin_manager.send_plugin_message(self._identifier, data)

    def preprocessing_success(self, results, path, preprocessor_args,
                              additional_metadata, is_manual_request):
        # save the newly created file.  This must be done before
        # exiting this callback because the target file isn't
        # guaranteed to exist later.
        try:
            new_path, new_name, metadata = self.save_preprocessed_file(
                path, preprocessor_args, results, additional_metadata)
        except TargetFileSaveError as e:
            data = {
                "message_type":
                "preprocessing-failed",
                "source_filename":
                self.preprocessing_job_source_file_path,
                "target_filename":
                self.preprocessing_job_target_file_name,
                "preprocessing_job_guid":
                self.preprocessing_job_guid,
                "message":
                'Unable to save the target file.  A file with the same name may be currently printing.'
            }
            self._plugin_manager.send_plugin_message(self._identifier, data)
            return

        if (((is_manual_request
              and self._delete_source_after_manual_processing) or
             (not is_manual_request
              and self._delete_source_after_automatic_processing))
                and self._file_manager.file_exists(
                    FileDestinations.LOCAL, path) and not path == new_path):
            if not self._get_is_printing(path):
                # if the file is selected, deselect it.
                if self._is_file_selected(path, FileDestinations.LOCAL):
                    self._printer.unselect_file()
                # delete the source file
                logger.info("Deleting source file at %s.", path)
                try:
                    self._file_manager.remove_file(FileDestinations.LOCAL,
                                                   path)
                except octoprint.filemanager.storage.StorageError:
                    logger.exception(
                        "Unable to delete the source file at '%s'", path)
            else:
                logger.exception(
                    "Unable to delete the source file at '%s'.  It is currently printing.",
                    path)

        if self._show_completed_notification:
            data = {
                "message_type": "preprocessing-success",
                "arc_welder_statistics": metadata,
                "path": new_path,
                "name": new_name,
                "origin": FileDestinations.LOCAL
            }
            self._plugin_manager.send_plugin_message(self._identifier, data)

    def preprocessing_completed(self):
        data = {"message_type": "preprocessing-complete"}
        self.preprocessing_job_guid = None
        self.preprocessing_job_source_file_path = None
        self.preprocessing_job_target_file_name = None
        self._plugin_manager.send_plugin_message(self._identifier, data)

    def preprocessing_failed(self, message):
        data = {
            "message_type": "preprocessing-failed",
            "source_filename": self.preprocessing_job_source_file_path,
            "target_filename": self.preprocessing_job_target_file_name,
            "preprocessing_job_guid": self.preprocessing_job_guid,
            "message": message
        }
        self._plugin_manager.send_plugin_message(self._identifier, data)

    def on_event(self, event, payload):

        # Need to use file added event to catch uploads and other non-upload methods of adding a file.
        if event == Events.FILE_ADDED:
            if not self._enabled or not self._auto_pre_processing_enabled:
                return
            # Note, 'target' is the key for FILE_UPLOADED, but 'storage' is the key for FILE_ADDED
            target = payload["storage"]
            path = payload["path"]
            name = payload["name"]

            if path == self.preprocessing_job_source_file_path or name == self.preprocessing_job_target_file_name:
                return

            if not octoprint.filemanager.valid_file_type(path, type="gcode"):
                return

            if not target == FileDestinations.LOCAL:
                return

            metadata = self._file_manager.get_metadata(target, path)
            if "arc_welder" in metadata:
                return
            # Extract only the supported metadata from the added file
            additional_metadata = self.get_additional_metadata(metadata)
            # Add this file to the processor queue.
            self.add_file_to_preprocessor_queue(path, additional_metadata,
                                                False)

    def get_additional_metadata(self, metadata):
        # list of supported metadata
        supported_metadata_keys = ['thumbnail', 'thumbnail_src']
        additional_metadata = {}
        # Create the additional metadata from the supported keys
        for key in supported_metadata_keys:
            if key in metadata:
                additional_metadata[key] = metadata[key]
        return additional_metadata

    def add_file_to_preprocessor_queue(self, path, additional_metadata,
                                       is_manual_request):
        # get the file by path
        # file = self._file_manager.get_file(FileDestinations.LOCAL, path)
        if self._get_is_printing():
            self.send_notification_toast(
                "warning",
                "Arc-Welder: Unable to Process",
                "Cannot preprocess gcode while a print is in progress because print quality may be affected.  The "
                "gcode will be processed as soon as the print has completed.",
                True,
                key="unable_to_process",
                close_keys=["unable_to_process"])

        logger.info("Received a new gcode file for processing.  FileName: %s.",
                    path)
        path_on_disk = self._file_manager.path_on_disk(FileDestinations.LOCAL,
                                                       path)

        # make sure the path starts with a / for compatibility
        if path[0] != '/':
            path = '/' + path

        preprocessor_args = self.get_preprocessor_arguments(path_on_disk)
        self._processing_queue.put(
            (path, preprocessor_args, additional_metadata, is_manual_request))

    def register_custom_routes(self, server_routes, *args, **kwargs):
        # version specific permission validator
        if LooseVersion(octoprint.server.VERSION) >= LooseVersion("1.4"):
            admin_validation_chain = [
                util.tornado.access_validation_factory(
                    app, util.flask.admin_validator),
            ]
        else:
            # the concept of granular permissions does not exist in this version of Octoprint.  Fallback to the
            # admin role
            def admin_permission_validator(flask_request):
                user = util.flask.get_flask_user_from_request(flask_request)
                if user is None or not user.is_authenticated(
                ) or not user.is_admin():
                    raise tornado.web.HTTPError(403)

            permission_validator = admin_permission_validator
            admin_validation_chain = [
                util.tornado.access_validation_factory(app,
                                                       permission_validator),
            ]
        return [(r"/downloadFile", ArcWelderLargeResponseHandler,
                 dict(request_callback=self.download_file_request,
                      as_attachment=True,
                      access_validation=util.tornado.validation_chain(
                          *admin_validation_chain)))]

        # ~~ software update hook

    arc_welder_update_info = dict(
        displayName="Arc Welder: Anti-Stutter",
        # version check: github repository
        type="github_release",
        user="******",
        repo="ArcWelderPlugin",
        pip=
        "https://github.com/FormerLurker/ArcWelderPlugin/archive/{target_version}.zip",
        stable_branch=dict(branch="master",
                           commitish=["master"],
                           name="Stable"),
        release_compare='custom',
        prerelease_branches=[
            dict(
                branch="rc/maintenance",
                commitish=["master", "rc/maintenance"
                           ],  # maintenance RCs (include master)
                name="Maintenance RCs"),
            dict(
                branch="rc/devel",
                commitish=["master", "rc/maintenance", "rc/devel"
                           ],  # devel & maintenance RCs (include master)
                name="Devel RCs")
        ],
    )

    def get_release_info(self):
        # Starting with V1.5.0 prerelease branches are supported!
        if LooseVersion(octoprint.server.VERSION) < LooseVersion("1.5.0"):
            # Hack to attempt to get pre-release branches to work prior to 1.5.0
            # get the checkout type from the software updater
            prerelease_channel = None
            is_prerelease = False
            # get this for reference.  Eventually I'll have to use it!
            # is the software update set to prerelease?

            if self._settings.global_get([
                    "plugins", "softwareupdate", "checks", "octoprint",
                    "prerelease"
            ]):
                # If it's a prerelease, look at the channel and configure the proper branch for Arc Welder
                prerelease_channel = self._settings.global_get([
                    "plugins", "softwareupdate", "checks", "octoprint",
                    "prerelease_channel"
                ])
                if prerelease_channel == "rc/maintenance":
                    is_prerelease = True
                    prerelease_channel = "rc/maintenance"
                elif prerelease_channel == "rc/devel":
                    is_prerelease = True
                    prerelease_channel = "rc/devel"
            ArcWelderPlugin.arc_welder_update_info[
                "displayVersion"] = self._plugin_version
            ArcWelderPlugin.arc_welder_update_info[
                "current"] = self._plugin_version
            ArcWelderPlugin.arc_welder_update_info[
                "prerelease"] = is_prerelease
            if prerelease_channel is not None:
                ArcWelderPlugin.arc_welder_update_info[
                    "prerelease_channel"] = prerelease_channel

        return dict(arc_welder=ArcWelderPlugin.arc_welder_update_info)

    def get_update_information(self):
        # moved most of the heavy lifting to get_latest, since I need to do a custom version compare.
        # AND I want to use the most recent software update release channel settings.
        return self.get_release_info()
Ejemplo n.º 9
0
            identity.provides.add(p.RoleNeed(role.name.upper()))
        logging.debug(
            "User has permissions: %s" %
            (", ".join([role.name for role in g.current_user.permissions])))
    else:
        logging.debug("User Has no permissions")
        pass
    return identity


@principal.identity_saver
def save_identity(identity):
    g.identity = identity


admin_permission = p.Permission(p.RoleNeed("ADMIN"))


def error_handler(f):
    auth_api.error_handler(f)


def login_required(f):
    return auth_api.login_required(f)


def hash_password(password):
    return bcrypt.hashpw(password.encode("utf-8"),
                         current_app.config["SECRET_KEY"])