365 lines
20 KiB
Python
365 lines
20 KiB
Python
# coding=utf-8
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__author__ = "Gina Häußge <osd@foosel.net>"
|
|
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
|
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
|
|
|
|
import logging
|
|
|
|
from flask import request, jsonify, make_response
|
|
from flask.ext.login import current_user
|
|
from werkzeug.exceptions import BadRequest
|
|
|
|
from octoprint.events import eventManager, Events
|
|
from octoprint.settings import settings, valid_boolean_trues
|
|
|
|
from octoprint.server import admin_permission, printer
|
|
from octoprint.server.api import api
|
|
from octoprint.server.util.flask import restricted_access, with_revalidation_checking
|
|
|
|
import octoprint.plugin
|
|
import octoprint.util
|
|
|
|
#~~ settings
|
|
|
|
def _lastmodified():
|
|
return settings().last_modified
|
|
|
|
def _etag(lm=None):
|
|
if lm is None:
|
|
lm = _lastmodified()
|
|
|
|
connection_options = printer.__class__.get_connection_options()
|
|
plugins = sorted(octoprint.plugin.plugin_manager().enabled_plugins)
|
|
if current_user is not None and not current_user.is_anonymous():
|
|
roles = sorted(current_user.roles)
|
|
else:
|
|
roles = []
|
|
|
|
import hashlib
|
|
hash = hashlib.sha1()
|
|
hash.update(str(lm))
|
|
hash.update(repr(settings().effective))
|
|
hash.update(repr(connection_options))
|
|
hash.update(repr(plugins))
|
|
hash.update(repr(roles))
|
|
return hash.hexdigest()
|
|
|
|
@api.route("/settings", methods=["GET"])
|
|
@with_revalidation_checking(etag_factory=_etag,
|
|
lastmodified_factory=_lastmodified,
|
|
unless=lambda: request.values.get("force", "false") in valid_boolean_trues)
|
|
def getSettings():
|
|
logger = logging.getLogger(__name__)
|
|
|
|
s = settings()
|
|
|
|
connectionOptions = printer.__class__.get_connection_options()
|
|
|
|
# NOTE: Remember to adjust the docs of the data model on the Settings API if anything
|
|
# is changed, added or removed here
|
|
|
|
data = {
|
|
"api": {
|
|
"enabled": s.getBoolean(["api", "enabled"]),
|
|
"key": s.get(["api", "key"]) if admin_permission.can() else "n/a",
|
|
"allowCrossOrigin": s.get(["api", "allowCrossOrigin"])
|
|
},
|
|
"appearance": {
|
|
"name": s.get(["appearance", "name"]),
|
|
"color": s.get(["appearance", "color"]),
|
|
"colorTransparent": s.getBoolean(["appearance", "colorTransparent"]),
|
|
"defaultLanguage": s.get(["appearance", "defaultLanguage"]),
|
|
"showFahrenheitAlso": s.getBoolean(["appearance", "showFahrenheitAlso"])
|
|
},
|
|
"printer": {
|
|
"defaultExtrusionLength": s.getInt(["printerParameters", "defaultExtrusionLength"])
|
|
},
|
|
"webcam": {
|
|
"streamUrl": s.get(["webcam", "stream"]),
|
|
"snapshotUrl": s.get(["webcam", "snapshot"]),
|
|
"ffmpegPath": s.get(["webcam", "ffmpeg"]),
|
|
"bitrate": s.get(["webcam", "bitrate"]),
|
|
"ffmpegThreads": s.get(["webcam", "ffmpegThreads"]),
|
|
"watermark": s.getBoolean(["webcam", "watermark"]),
|
|
"flipH": s.getBoolean(["webcam", "flipH"]),
|
|
"flipV": s.getBoolean(["webcam", "flipV"]),
|
|
"rotate90": s.getBoolean(["webcam", "rotate90"])
|
|
},
|
|
"feature": {
|
|
"gcodeViewer": s.getBoolean(["gcodeViewer", "enabled"]),
|
|
"sizeThreshold": s.getInt(["gcodeViewer", "sizeThreshold"]),
|
|
"mobileSizeThreshold": s.getInt(["gcodeViewer", "mobileSizeThreshold"]),
|
|
"temperatureGraph": s.getBoolean(["feature", "temperatureGraph"]),
|
|
"waitForStart": s.getBoolean(["feature", "waitForStartOnConnect"]),
|
|
"alwaysSendChecksum": s.getBoolean(["feature", "alwaysSendChecksum"]),
|
|
"neverSendChecksum": s.getBoolean(["feature", "neverSendChecksum"]),
|
|
"sdSupport": s.getBoolean(["feature", "sdSupport"]),
|
|
"sdRelativePath": s.getBoolean(["feature", "sdRelativePath"]),
|
|
"sdAlwaysAvailable": s.getBoolean(["feature", "sdAlwaysAvailable"]),
|
|
"swallowOkAfterResend": s.getBoolean(["feature", "swallowOkAfterResend"]),
|
|
"repetierTargetTemp": s.getBoolean(["feature", "repetierTargetTemp"]),
|
|
"externalHeatupDetection": s.getBoolean(["feature", "externalHeatupDetection"]),
|
|
"keyboardControl": s.getBoolean(["feature", "keyboardControl"]),
|
|
"pollWatched": s.getBoolean(["feature", "pollWatched"]),
|
|
"ignoreIdenticalResends": s.getBoolean(["feature", "ignoreIdenticalResends"]),
|
|
"modelSizeDetection": s.getBoolean(["feature", "modelSizeDetection"])
|
|
},
|
|
"serial": {
|
|
"port": connectionOptions["portPreference"],
|
|
"baudrate": connectionOptions["baudratePreference"],
|
|
"portOptions": connectionOptions["ports"],
|
|
"baudrateOptions": connectionOptions["baudrates"],
|
|
"autoconnect": s.getBoolean(["serial", "autoconnect"]),
|
|
"timeoutConnection": s.getFloat(["serial", "timeout", "connection"]),
|
|
"timeoutDetection": s.getFloat(["serial", "timeout", "detection"]),
|
|
"timeoutCommunication": s.getFloat(["serial", "timeout", "communication"]),
|
|
"timeoutTemperature": s.getFloat(["serial", "timeout", "temperature"]),
|
|
"timeoutTemperatureTargetSet": s.getFloat(["serial", "timeout", "temperatureTargetSet"]),
|
|
"timeoutSdStatus": s.getFloat(["serial", "timeout", "sdStatus"]),
|
|
"log": s.getBoolean(["serial", "log"]),
|
|
"additionalPorts": s.get(["serial", "additionalPorts"]),
|
|
"additionalBaudrates": s.get(["serial", "additionalBaudrates"]),
|
|
"longRunningCommands": s.get(["serial", "longRunningCommands"]),
|
|
"checksumRequiringCommands": s.get(["serial", "checksumRequiringCommands"]),
|
|
"helloCommand": s.get(["serial", "helloCommand"]),
|
|
"ignoreErrorsFromFirmware": s.getBoolean(["serial", "ignoreErrorsFromFirmware"]),
|
|
"disconnectOnErrors": s.getBoolean(["serial", "disconnectOnErrors"]),
|
|
"triggerOkForM29": s.getBoolean(["serial", "triggerOkForM29"]),
|
|
"supportResendsWithoutOk": s.getBoolean(["serial", "supportResendsWithoutOk"]),
|
|
"maxTimeoutsIdle": s.getInt(["serial", "maxCommunicationTimeouts", "idle"]),
|
|
"maxTimeoutsPrinting": s.getInt(["serial", "maxCommunicationTimeouts", "printing"]),
|
|
"maxTimeoutsLong": s.getInt(["serial", "maxCommunicationTimeouts", "long"])
|
|
},
|
|
"folder": {
|
|
"uploads": s.getBaseFolder("uploads"),
|
|
"timelapse": s.getBaseFolder("timelapse"),
|
|
"timelapseTmp": s.getBaseFolder("timelapse_tmp"),
|
|
"logs": s.getBaseFolder("logs"),
|
|
"watched": s.getBaseFolder("watched")
|
|
},
|
|
"temperature": {
|
|
"profiles": s.get(["temperature", "profiles"]),
|
|
"cutoff": s.getInt(["temperature", "cutoff"])
|
|
},
|
|
"system": {
|
|
"actions": s.get(["system", "actions"]),
|
|
"events": s.get(["system", "events"])
|
|
},
|
|
"terminalFilters": s.get(["terminalFilters"]),
|
|
"scripts": {
|
|
"gcode": {
|
|
"afterPrinterConnected": None,
|
|
"beforePrinterDisconnected": None,
|
|
"beforePrintStarted": None,
|
|
"afterPrintCancelled": None,
|
|
"afterPrintDone": None,
|
|
"beforePrintPaused": None,
|
|
"afterPrintResumed": None,
|
|
"snippets": dict()
|
|
}
|
|
},
|
|
"server": {
|
|
"commands": {
|
|
"systemShutdownCommand": s.get(["server", "commands", "systemShutdownCommand"]),
|
|
"systemRestartCommand": s.get(["server", "commands", "systemRestartCommand"]),
|
|
"serverRestartCommand": s.get(["server", "commands", "serverRestartCommand"])
|
|
},
|
|
"diskspace": {
|
|
"warning": s.getInt(["server", "diskspace", "warning"]),
|
|
"critical": s.getInt(["server", "diskspace", "critical"])
|
|
}
|
|
}
|
|
}
|
|
|
|
gcode_scripts = s.listScripts("gcode")
|
|
if gcode_scripts:
|
|
data["scripts"] = dict(gcode=dict())
|
|
for name in gcode_scripts:
|
|
data["scripts"]["gcode"][name] = s.loadScript("gcode", name, source=True)
|
|
|
|
def process_plugin_result(name, result):
|
|
if result:
|
|
try:
|
|
jsonify(test=result)
|
|
except:
|
|
logger.exception("Error while jsonifying settings from plugin {}, please contact the plugin author about this".format(name))
|
|
|
|
if not "plugins" in data:
|
|
data["plugins"] = dict()
|
|
if "__enabled" in result:
|
|
del result["__enabled"]
|
|
data["plugins"][name] = result
|
|
|
|
for plugin in octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SettingsPlugin):
|
|
try:
|
|
result = plugin.on_settings_load()
|
|
process_plugin_result(plugin._identifier, result)
|
|
except TypeError:
|
|
logger.warn("Could not load settings for plugin {name} ({version}) since it called super(...)".format(name=plugin._plugin_name, version=plugin._plugin_version))
|
|
logger.warn("in a way which has issues due to OctoPrint's dynamic reloading after plugin operations.")
|
|
logger.warn("Please contact the plugin's author and ask to update the plugin to use a direct call like")
|
|
logger.warn("octoprint.plugin.SettingsPlugin.on_settings_load(self) instead.")
|
|
except:
|
|
logger.exception("Could not load settings for plugin {name} ({version})".format(version=plugin._plugin_version, name=plugin._plugin_name))
|
|
|
|
return jsonify(data)
|
|
|
|
|
|
@api.route("/settings", methods=["POST"])
|
|
@restricted_access
|
|
@admin_permission.require(403)
|
|
def setSettings():
|
|
if not "application/json" in request.headers["Content-Type"]:
|
|
return make_response("Expected content-type JSON", 400)
|
|
|
|
try:
|
|
data = request.json
|
|
except BadRequest:
|
|
return make_response("Malformed JSON body in request", 400)
|
|
|
|
_saveSettings(data)
|
|
return getSettings()
|
|
|
|
def _saveSettings(data):
|
|
logger = logging.getLogger(__name__)
|
|
|
|
s = settings()
|
|
|
|
# NOTE: Remember to adjust the docs of the data model on the Settings API if anything
|
|
# is changed, added or removed here
|
|
|
|
if "api" in data.keys():
|
|
if "enabled" in data["api"].keys(): s.setBoolean(["api", "enabled"], data["api"]["enabled"])
|
|
if "key" in data["api"].keys(): s.set(["api", "key"], data["api"]["key"], True)
|
|
if "allowCrossOrigin" in data["api"].keys(): s.setBoolean(["api", "allowCrossOrigin"], data["api"]["allowCrossOrigin"])
|
|
|
|
if "appearance" in data.keys():
|
|
if "name" in data["appearance"].keys(): s.set(["appearance", "name"], data["appearance"]["name"])
|
|
if "color" in data["appearance"].keys(): s.set(["appearance", "color"], data["appearance"]["color"])
|
|
if "colorTransparent" in data["appearance"].keys(): s.setBoolean(["appearance", "colorTransparent"], data["appearance"]["colorTransparent"])
|
|
if "defaultLanguage" in data["appearance"]: s.set(["appearance", "defaultLanguage"], data["appearance"]["defaultLanguage"])
|
|
if "showFahrenheitAlso" in data["appearance"]: s.setBoolean(["appearance", "showFahrenheitAlso"], data["appearance"]["showFahrenheitAlso"])
|
|
|
|
if "printer" in data.keys():
|
|
if "defaultExtrusionLength" in data["printer"]: s.setInt(["printerParameters", "defaultExtrusionLength"], data["printer"]["defaultExtrusionLength"])
|
|
|
|
if "webcam" in data.keys():
|
|
if "streamUrl" in data["webcam"].keys(): s.set(["webcam", "stream"], data["webcam"]["streamUrl"])
|
|
if "snapshotUrl" in data["webcam"].keys(): s.set(["webcam", "snapshot"], data["webcam"]["snapshotUrl"])
|
|
if "ffmpegPath" in data["webcam"].keys(): s.set(["webcam", "ffmpeg"], data["webcam"]["ffmpegPath"])
|
|
if "bitrate" in data["webcam"].keys(): s.set(["webcam", "bitrate"], data["webcam"]["bitrate"])
|
|
if "ffmpegThreads" in data["webcam"].keys(): s.setInt(["webcam", "ffmpegThreads"], data["webcam"]["ffmpegThreads"])
|
|
if "watermark" in data["webcam"].keys(): s.setBoolean(["webcam", "watermark"], data["webcam"]["watermark"])
|
|
if "flipH" in data["webcam"].keys(): s.setBoolean(["webcam", "flipH"], data["webcam"]["flipH"])
|
|
if "flipV" in data["webcam"].keys(): s.setBoolean(["webcam", "flipV"], data["webcam"]["flipV"])
|
|
if "rotate90" in data["webcam"].keys(): s.setBoolean(["webcam", "rotate90"], data["webcam"]["rotate90"])
|
|
|
|
if "feature" in data.keys():
|
|
if "gcodeViewer" in data["feature"].keys(): s.setBoolean(["gcodeViewer", "enabled"], data["feature"]["gcodeViewer"])
|
|
if "sizeThreshold" in data["feature"].keys(): s.setInt(["gcodeViewer", "sizeThreshold"], data["feature"]["sizeThreshold"])
|
|
if "mobileSizeThreshold" in data["feature"].keys(): s.setInt(["gcodeViewer", "mobileSizeThreshold"], data["feature"]["mobileSizeThreshold"])
|
|
if "temperatureGraph" in data["feature"].keys(): s.setBoolean(["feature", "temperatureGraph"], data["feature"]["temperatureGraph"])
|
|
if "waitForStart" in data["feature"].keys(): s.setBoolean(["feature", "waitForStartOnConnect"], data["feature"]["waitForStart"])
|
|
if "alwaysSendChecksum" in data["feature"].keys(): s.setBoolean(["feature", "alwaysSendChecksum"], data["feature"]["alwaysSendChecksum"])
|
|
if "neverSendChecksum" in data["feature"].keys(): s.setBoolean(["feature", "neverSendChecksum"], data["feature"]["neverSendChecksum"])
|
|
if "sdSupport" in data["feature"].keys(): s.setBoolean(["feature", "sdSupport"], data["feature"]["sdSupport"])
|
|
if "sdRelativePath" in data["feature"].keys(): s.setBoolean(["feature", "sdRelativePath"], data["feature"]["sdRelativePath"])
|
|
if "sdAlwaysAvailable" in data["feature"].keys(): s.setBoolean(["feature", "sdAlwaysAvailable"], data["feature"]["sdAlwaysAvailable"])
|
|
if "swallowOkAfterResend" in data["feature"].keys(): s.setBoolean(["feature", "swallowOkAfterResend"], data["feature"]["swallowOkAfterResend"])
|
|
if "repetierTargetTemp" in data["feature"].keys(): s.setBoolean(["feature", "repetierTargetTemp"], data["feature"]["repetierTargetTemp"])
|
|
if "externalHeatupDetection" in data["feature"].keys(): s.setBoolean(["feature", "externalHeatupDetection"], data["feature"]["externalHeatupDetection"])
|
|
if "keyboardControl" in data["feature"].keys(): s.setBoolean(["feature", "keyboardControl"], data["feature"]["keyboardControl"])
|
|
if "pollWatched" in data["feature"]: s.setBoolean(["feature", "pollWatched"], data["feature"]["pollWatched"])
|
|
if "ignoreIdenticalResends" in data["feature"]: s.setBoolean(["feature", "ignoreIdenticalResends"], data["feature"]["ignoreIdenticalResends"])
|
|
if "modelSizeDetection" in data["feature"]: s.setBoolean(["feature", "modelSizeDetection"], data["feature"]["modelSizeDetection"])
|
|
|
|
if "serial" in data.keys():
|
|
if "autoconnect" in data["serial"].keys(): s.setBoolean(["serial", "autoconnect"], data["serial"]["autoconnect"])
|
|
if "port" in data["serial"].keys(): s.set(["serial", "port"], data["serial"]["port"])
|
|
if "baudrate" in data["serial"].keys(): s.setInt(["serial", "baudrate"], data["serial"]["baudrate"])
|
|
if "timeoutConnection" in data["serial"].keys(): s.setFloat(["serial", "timeout", "connection"], data["serial"]["timeoutConnection"])
|
|
if "timeoutDetection" in data["serial"].keys(): s.setFloat(["serial", "timeout", "detection"], data["serial"]["timeoutDetection"])
|
|
if "timeoutCommunication" in data["serial"].keys(): s.setFloat(["serial", "timeout", "communication"], data["serial"]["timeoutCommunication"])
|
|
if "timeoutTemperature" in data["serial"].keys(): s.setFloat(["serial", "timeout", "temperature"], data["serial"]["timeoutTemperature"])
|
|
if "timeoutTemperatureTargetSet" in data["serial"].keys(): s.setFloat(["serial", "timeout", "temperatureTargetSet"], data["serial"]["timeoutTemperatureTargetSet"])
|
|
if "timeoutSdStatus" in data["serial"].keys(): s.setFloat(["serial", "timeout", "sdStatus"], data["serial"]["timeoutSdStatus"])
|
|
if "additionalPorts" in data["serial"] and isinstance(data["serial"]["additionalPorts"], (list, tuple)): s.set(["serial", "additionalPorts"], data["serial"]["additionalPorts"])
|
|
if "additionalBaudrates" in data["serial"] and isinstance(data["serial"]["additionalBaudrates"], (list, tuple)): s.set(["serial", "additionalBaudrates"], data["serial"]["additionalBaudrates"])
|
|
if "longRunningCommands" in data["serial"] and isinstance(data["serial"]["longRunningCommands"], (list, tuple)): s.set(["serial", "longRunningCommands"], data["serial"]["longRunningCommands"])
|
|
if "checksumRequiringCommands" in data["serial"] and isinstance(data["serial"]["checksumRequiringCommands"], (list, tuple)): s.set(["serial", "checksumRequiringCommands"], data["serial"]["checksumRequiringCommands"])
|
|
if "helloCommand" in data["serial"]: s.set(["serial", "helloCommand"], data["serial"]["helloCommand"])
|
|
if "ignoreErrorsFromFirmware" in data["serial"]: s.setBoolean(["serial", "ignoreErrorsFromFirmware"], data["serial"]["ignoreErrorsFromFirmware"])
|
|
if "disconnectOnErrors" in data["serial"]: s.setBoolean(["serial", "disconnectOnErrors"], data["serial"]["disconnectOnErrors"])
|
|
if "triggerOkForM29" in data["serial"]: s.setBoolean(["serial", "triggerOkForM29"], data["serial"]["triggerOkForM29"])
|
|
if "supportResendsWithoutOk" in data["serial"]: s.setBoolean(["serial", "supportResendsWithoutOk"], data["serial"]["supportResendsWithoutOk"])
|
|
if "maxTimeoutsIdle" in data["serial"]: s.setInt(["serial", "maxCommunicationTimeouts", "idle"], data["serial"]["maxTimeoutsIdle"])
|
|
if "maxTimeoutsPrinting" in data["serial"]: s.setInt(["serial", "maxCommunicationTimeouts", "printing"], data["serial"]["maxTimeoutsPrinting"])
|
|
if "maxTimeoutsLong" in data["serial"]: s.setInt(["serial", "maxCommunicationTimeouts", "long"], data["serial"]["maxTimeoutsLong"])
|
|
|
|
oldLog = s.getBoolean(["serial", "log"])
|
|
if "log" in data["serial"].keys(): s.setBoolean(["serial", "log"], data["serial"]["log"])
|
|
if oldLog and not s.getBoolean(["serial", "log"]):
|
|
# disable debug logging to serial.log
|
|
logging.getLogger("SERIAL").debug("Disabling serial logging")
|
|
logging.getLogger("SERIAL").setLevel(logging.CRITICAL)
|
|
elif not oldLog and s.getBoolean(["serial", "log"]):
|
|
# enable debug logging to serial.log
|
|
logging.getLogger("SERIAL").setLevel(logging.DEBUG)
|
|
logging.getLogger("SERIAL").debug("Enabling serial logging")
|
|
|
|
if "folder" in data.keys():
|
|
if "uploads" in data["folder"].keys(): s.setBaseFolder("uploads", data["folder"]["uploads"])
|
|
if "timelapse" in data["folder"].keys(): s.setBaseFolder("timelapse", data["folder"]["timelapse"])
|
|
if "timelapseTmp" in data["folder"].keys(): s.setBaseFolder("timelapse_tmp", data["folder"]["timelapseTmp"])
|
|
if "logs" in data["folder"].keys(): s.setBaseFolder("logs", data["folder"]["logs"])
|
|
if "watched" in data["folder"].keys(): s.setBaseFolder("watched", data["folder"]["watched"])
|
|
|
|
if "temperature" in data.keys():
|
|
if "profiles" in data["temperature"].keys(): s.set(["temperature", "profiles"], data["temperature"]["profiles"])
|
|
if "cutoff" in data["temperature"].keys(): s.setInt(["temperature", "cutoff"], data["temperature"]["cutoff"])
|
|
|
|
if "terminalFilters" in data.keys():
|
|
s.set(["terminalFilters"], data["terminalFilters"])
|
|
|
|
if "system" in data.keys():
|
|
if "actions" in data["system"].keys(): s.set(["system", "actions"], data["system"]["actions"])
|
|
if "events" in data["system"].keys(): s.set(["system", "events"], data["system"]["events"])
|
|
|
|
if "scripts" in data:
|
|
if "gcode" in data["scripts"] and isinstance(data["scripts"]["gcode"], dict):
|
|
for name, script in data["scripts"]["gcode"].items():
|
|
if name == "snippets":
|
|
continue
|
|
s.saveScript("gcode", name, script.replace("\r\n", "\n").replace("\r", "\n"))
|
|
|
|
if "server" in data:
|
|
if "commands" in data["server"]:
|
|
if "systemShutdownCommand" in data["server"]["commands"].keys(): s.set(["server", "commands", "systemShutdownCommand"], data["server"]["commands"]["systemShutdownCommand"])
|
|
if "systemRestartCommand" in data["server"]["commands"].keys(): s.set(["server", "commands", "systemRestartCommand"], data["server"]["commands"]["systemRestartCommand"])
|
|
if "serverRestartCommand" in data["server"]["commands"].keys(): s.set(["server", "commands", "serverRestartCommand"], data["server"]["commands"]["serverRestartCommand"])
|
|
if "diskspace" in data["server"]:
|
|
if "warning" in data["server"]["diskspace"]: s.setInt(["server", "diskspace", "warning"], data["server"]["diskspace"]["warning"])
|
|
if "critical" in data["server"]["diskspace"]: s.setInt(["server", "diskspace", "critical"], data["server"]["diskspace"]["critical"])
|
|
|
|
if "plugins" in data:
|
|
for plugin in octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SettingsPlugin):
|
|
plugin_id = plugin._identifier
|
|
if plugin_id in data["plugins"]:
|
|
try:
|
|
plugin.on_settings_save(data["plugins"][plugin_id])
|
|
except TypeError:
|
|
logger.warn("Could not save settings for plugin {name} ({version}) since it called super(...)".format(name=plugin._plugin_name, version=plugin._plugin_version))
|
|
logger.warn("in a way which has issues due to OctoPrint's dynamic reloading after plugin operations.")
|
|
logger.warn("Please contact the plugin's author and ask to update the plugin to use a direct call like")
|
|
logger.warn("octoprint.plugin.SettingsPlugin.on_settings_save(self, data) instead.")
|
|
except:
|
|
logger.exception("Could not save settings for plugin {name} ({version})".format(version=plugin._plugin_version, name=plugin._plugin_name))
|
|
|
|
if s.save():
|
|
payload = dict(
|
|
config_hash=s.config_hash,
|
|
effective_hash=s.effective_hash
|
|
)
|
|
eventManager().fire(Events.SETTINGS_UPDATED, payload=payload)
|