def authorize_item_action(cls, item): """ Perform access authorization for current user to particular item. """ # Each user must be able to view his/her account. permission_me = flask_principal.Permission( flask_principal.UserNeed(item.id)) # Managers of the groups the user is member of may view his/her account. needs = [mydojo.auth.ManagementNeed(x.id) for x in item.memberships] permission_mngr = flask_principal.Permission(*needs) return mydojo.auth.PERMISSION_ADMIN.can() or permission_mngr.can( ) or permission_me.can()
def authorize_item_action(cls, item=None): """ Perform access authorization for current user to particular item. """ permission_me = flask_principal.Permission( flask_principal.UserNeed(item.id)) return mydojo.auth.PERMISSION_ADMIN.can() or permission_me.can()
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)
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
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()
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(
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()
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"])