Added automatic migration of old event subscription format to the new one
This commit is contained in:
parent
92c7f886df
commit
e7088ef06c
3 changed files with 150 additions and 59 deletions
|
|
@ -203,36 +203,42 @@ class DebugEventListener(GenericEventListener):
|
||||||
|
|
||||||
|
|
||||||
class CommandTrigger(GenericEventListener):
|
class CommandTrigger(GenericEventListener):
|
||||||
def __init__(self, triggerType, printer):
|
def __init__(self, printer):
|
||||||
GenericEventListener.__init__(self)
|
GenericEventListener.__init__(self)
|
||||||
self._printer = printer
|
self._printer = printer
|
||||||
self._subscriptions = {}
|
self._subscriptions = {}
|
||||||
|
|
||||||
self._initSubscriptions(triggerType)
|
self._initSubscriptions()
|
||||||
|
|
||||||
def _initSubscriptions(self, triggerType):
|
def _initSubscriptions(self):
|
||||||
"""
|
"""
|
||||||
Subscribes all events as defined in "events > $triggerType > subscriptions" in the settings with their
|
Subscribes all events as defined in "events > $triggerType > subscriptions" in the settings with their
|
||||||
respective commands.
|
respective commands.
|
||||||
"""
|
"""
|
||||||
if not settings().get(["events", triggerType]):
|
if not settings().get(["events"]):
|
||||||
return
|
return
|
||||||
|
|
||||||
if not settings().getBoolean(["events", triggerType, "enabled"]):
|
if not settings().getBoolean(["events", "enabled"]):
|
||||||
return
|
return
|
||||||
|
|
||||||
eventsToSubscribe = []
|
eventsToSubscribe = []
|
||||||
for subscription in settings().get(["events", triggerType, "subscriptions"]):
|
for subscription in settings().get(["events", "subscriptions"]):
|
||||||
if not "event" in subscription.keys() or not "command" in subscription.keys():
|
if not "event" in subscription.keys() or not "command" in subscription.keys() \
|
||||||
self._logger.info("Invalid %s, missing either event or command: %r" % (triggerType, subscription))
|
or not "type" in subscription.keys() or not subscription["type"] in ["system", "gcode"]:
|
||||||
|
self._logger.info("Invalid command trigger, missing either event, type or command or type is invalid: %r" % subscription)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "enabled" in subscription.keys() and not subscription["enabled"]:
|
||||||
|
self._logger.info("Disabled command trigger: %r" % subscription)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
event = subscription["event"]
|
event = subscription["event"]
|
||||||
command = subscription["command"]
|
command = subscription["command"]
|
||||||
|
commandType = subscription["type"]
|
||||||
|
|
||||||
if not event in self._subscriptions.keys():
|
if not event in self._subscriptions.keys():
|
||||||
self._subscriptions[event] = []
|
self._subscriptions[event] = []
|
||||||
self._subscriptions[event].append(command)
|
self._subscriptions[event].append((command, commandType))
|
||||||
|
|
||||||
if not event in eventsToSubscribe:
|
if not event in eventsToSubscribe:
|
||||||
eventsToSubscribe.append(event)
|
eventsToSubscribe.append(event)
|
||||||
|
|
@ -250,18 +256,48 @@ class CommandTrigger(GenericEventListener):
|
||||||
if not event in self._subscriptions:
|
if not event in self._subscriptions:
|
||||||
return
|
return
|
||||||
|
|
||||||
for command in self._subscriptions[event]:
|
for command, commandType in self._subscriptions[event]:
|
||||||
try:
|
try:
|
||||||
processedCommand = self._processCommand(command, payload)
|
if isinstance(command, (tuple, list, set)):
|
||||||
self.executeCommand(processedCommand)
|
processedCommand = []
|
||||||
|
for c in command:
|
||||||
|
processedCommand.append(self._processCommand(c, payload))
|
||||||
|
else:
|
||||||
|
processedCommand = self._processCommand(command, payload)
|
||||||
|
self.executeCommand(processedCommand, commandType)
|
||||||
except KeyError, e:
|
except KeyError, e:
|
||||||
self._logger.warn("There was an error processing one or more placeholders in the following command: %s" % command)
|
self._logger.warn("There was an error processing one or more placeholders in the following command: %s" % command)
|
||||||
|
|
||||||
def executeCommand(self, command):
|
def executeCommand(self, command, commandType):
|
||||||
"""
|
if commandType == "system":
|
||||||
Not implemented, override in child classes
|
self._executeSystemCommand(command)
|
||||||
"""
|
elif commandType == "gcode":
|
||||||
pass
|
self._executeGcodeCommand(command)
|
||||||
|
|
||||||
|
def _executeSystemCommand(self, command):
|
||||||
|
def commandExecutioner(command):
|
||||||
|
self._logger.info("Executing system command: %s" % command)
|
||||||
|
subprocess.Popen(command, shell=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if isinstance(command, (list, tuple, set)):
|
||||||
|
for c in command:
|
||||||
|
commandExecutioner(c)
|
||||||
|
else:
|
||||||
|
commandExecutioner(command)
|
||||||
|
except subprocess.CalledProcessError, e:
|
||||||
|
self._logger.warn("Command failed with return code %i: %s" % (e.returncode, e.message))
|
||||||
|
except Exception, ex:
|
||||||
|
self._logger.exception("Command failed")
|
||||||
|
|
||||||
|
def _executeGcodeCommand(self, command):
|
||||||
|
commands = [command]
|
||||||
|
if isinstance(command, (list, tuple, set)):
|
||||||
|
self.logger.debug("Executing GCode commands: %r" % command)
|
||||||
|
commands = list(command)
|
||||||
|
else:
|
||||||
|
self._logger.debug("Executing GCode command: %s" % command)
|
||||||
|
self._printer.commands(commands)
|
||||||
|
|
||||||
def _processCommand(self, command, payload):
|
def _processCommand(self, command, payload):
|
||||||
"""
|
"""
|
||||||
|
|
@ -302,34 +338,3 @@ class CommandTrigger(GenericEventListener):
|
||||||
params.update(payload)
|
params.update(payload)
|
||||||
|
|
||||||
return command.format(**params)
|
return command.format(**params)
|
||||||
|
|
||||||
|
|
||||||
class SystemCommandTrigger(CommandTrigger):
|
|
||||||
"""
|
|
||||||
Performs configured system commands for configured events.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, printer):
|
|
||||||
CommandTrigger.__init__(self, "systemCommandTrigger", printer)
|
|
||||||
|
|
||||||
def executeCommand(self, command):
|
|
||||||
try:
|
|
||||||
self._logger.info("Executing system command: %s" % command)
|
|
||||||
subprocess.Popen(command, shell=True)
|
|
||||||
except subprocess.CalledProcessError, e:
|
|
||||||
self._logger.warn("Command failed with return code %i: %s" % (e.returncode, e.message))
|
|
||||||
except Exception, ex:
|
|
||||||
self._logger.exception("Command failed")
|
|
||||||
|
|
||||||
|
|
||||||
class GcodeCommandTrigger(CommandTrigger):
|
|
||||||
"""
|
|
||||||
Sends configured GCODE commands to the printer for configured events.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, printer):
|
|
||||||
CommandTrigger.__init__(self, "gcodeCommandTrigger", printer)
|
|
||||||
|
|
||||||
def executeCommand(self, command):
|
|
||||||
self._logger.debug("Executing GCode command: %s" % command)
|
|
||||||
self._printer.commands(command.split(","))
|
|
||||||
|
|
|
||||||
|
|
@ -144,9 +144,8 @@ class Server():
|
||||||
# configure timelapse
|
# configure timelapse
|
||||||
octoprint.timelapse.configureTimelapse()
|
octoprint.timelapse.configureTimelapse()
|
||||||
|
|
||||||
# setup system and gcode command triggers
|
# setup command triggers
|
||||||
events.SystemCommandTrigger(printer)
|
events.CommandTrigger(printer)
|
||||||
events.GcodeCommandTrigger(printer)
|
|
||||||
if self._debug:
|
if self._debug:
|
||||||
events.DebugEventListener()
|
events.DebugEventListener()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,12 +125,8 @@ default_settings = {
|
||||||
"config": "/default/path/to/your/cura/config.ini"
|
"config": "/default/path/to/your/cura/config.ini"
|
||||||
},
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"systemCommandTrigger": {
|
"enabled": False,
|
||||||
"enabled": False
|
"subscriptions": []
|
||||||
},
|
|
||||||
"gcodeCommandTrigger": {
|
|
||||||
"enabled": False
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"enabled": False,
|
"enabled": False,
|
||||||
|
|
@ -173,7 +169,7 @@ class Settings(object):
|
||||||
self._configfile = configfile
|
self._configfile = configfile
|
||||||
else:
|
else:
|
||||||
self._configfile = os.path.join(self.settings_dir, "config.yaml")
|
self._configfile = os.path.join(self.settings_dir, "config.yaml")
|
||||||
self.load()
|
self.load(migrate=True)
|
||||||
|
|
||||||
def _init_settings_dir(self, basedir):
|
def _init_settings_dir(self, basedir):
|
||||||
if basedir is not None:
|
if basedir is not None:
|
||||||
|
|
@ -189,7 +185,7 @@ class Settings(object):
|
||||||
|
|
||||||
#~~ load and save
|
#~~ load and save
|
||||||
|
|
||||||
def load(self):
|
def load(self, migrate=False):
|
||||||
if os.path.exists(self._configfile) and os.path.isfile(self._configfile):
|
if os.path.exists(self._configfile) and os.path.isfile(self._configfile):
|
||||||
with open(self._configfile, "r") as f:
|
with open(self._configfile, "r") as f:
|
||||||
self._config = yaml.safe_load(f)
|
self._config = yaml.safe_load(f)
|
||||||
|
|
@ -197,6 +193,97 @@ class Settings(object):
|
||||||
if not self._config:
|
if not self._config:
|
||||||
self._config = {}
|
self._config = {}
|
||||||
|
|
||||||
|
if migrate:
|
||||||
|
self._migrateConfig()
|
||||||
|
|
||||||
|
def _migrateConfig(self):
|
||||||
|
if not self._config:
|
||||||
|
return
|
||||||
|
|
||||||
|
if "events" in self._config.keys() and ("gcodeCommandTrigger" in self._config["events"] or "systemCommandTrigger" in self._config["events"]):
|
||||||
|
self._logger.info("Migrating config (event subscriptions)...")
|
||||||
|
|
||||||
|
# migrate event hooks to new format
|
||||||
|
placeholderRe = re.compile("%\((.*?)\)s")
|
||||||
|
|
||||||
|
eventNameReplacements = {
|
||||||
|
"ClientOpen": "ClientOpened",
|
||||||
|
"TransferStart": "TransferStarted"
|
||||||
|
}
|
||||||
|
payloadDataReplacements = {
|
||||||
|
"Upload": {"data": "{file}", "filename": "{file}"},
|
||||||
|
"Connected": {"data": "{port} at {baudrate} baud"},
|
||||||
|
"FileSelected": {"data": "{file}", "filename": "{file}"},
|
||||||
|
"TransferStarted": {"data": "{remote}", "filename": "{remote}"},
|
||||||
|
"TransferDone": {"data": "{remote}", "filename": "{remote}"},
|
||||||
|
"ZChange": {"data": "{new}"},
|
||||||
|
"CaptureStart": {"data": "{file}"},
|
||||||
|
"CaptureDone": {"data": "{file}"},
|
||||||
|
"MovieDone": {"data": "{movie}", "filename": "{gcode}"},
|
||||||
|
"Error": {"data": "{error}"},
|
||||||
|
"PrintStarted": {"data": "{file}", "filename": "{file}"},
|
||||||
|
"PrintDone": {"data": "{file}", "filename": "{file}"},
|
||||||
|
}
|
||||||
|
|
||||||
|
def migrateEventHook(event, command):
|
||||||
|
# migrate placeholders
|
||||||
|
command = placeholderRe.sub("{__\\1}", command)
|
||||||
|
|
||||||
|
# migrate event names
|
||||||
|
if event in eventNameReplacements:
|
||||||
|
event = eventNameReplacements["event"]
|
||||||
|
|
||||||
|
# migrate payloads to more specific placeholders
|
||||||
|
if event in payloadDataReplacements:
|
||||||
|
for key in payloadDataReplacements[event]:
|
||||||
|
command = command.replace("{__%s}" % key, payloadDataReplacements[event][key])
|
||||||
|
|
||||||
|
# return processed tuple
|
||||||
|
return event, command
|
||||||
|
|
||||||
|
disableSystemCommands = False
|
||||||
|
if "systemCommandTrigger" in self._config["events"] and "enabled" in self._config["events"]["systemCommandTrigger"]:
|
||||||
|
disableSystemCommands = not self._config["events"]["systemCommandTrigger"]["enabled"]
|
||||||
|
|
||||||
|
disableGcodeCommands = False
|
||||||
|
if "gcodeCommandTrigger" in self._config["events"] and "enabled" in self._config["events"]["gcodeCommandTrigger"]:
|
||||||
|
disableGcodeCommands = not self._config["events"]["gcodeCommandTrigger"]["enabled"]
|
||||||
|
|
||||||
|
disableAllCommands = disableSystemCommands and disableGcodeCommands
|
||||||
|
newEvents = {
|
||||||
|
"enabled": not disableAllCommands,
|
||||||
|
"subscriptions": []
|
||||||
|
}
|
||||||
|
|
||||||
|
if "systemCommandTrigger" in self._config["events"] and "subscriptions" in self._config["events"]["systemCommandTrigger"]:
|
||||||
|
for trigger in self._config["events"]["systemCommandTrigger"]["subscriptions"]:
|
||||||
|
if not ("event" in trigger and "command" in trigger):
|
||||||
|
continue
|
||||||
|
|
||||||
|
newTrigger = {"type": "system"}
|
||||||
|
if disableSystemCommands and not disableAllCommands:
|
||||||
|
newTrigger["enabled"] = False
|
||||||
|
|
||||||
|
newTrigger["event"], newTrigger["command"] = migrateEventHook(trigger["event"], trigger["command"])
|
||||||
|
newEvents["subscriptions"].append(newTrigger)
|
||||||
|
|
||||||
|
if "gcodeCommandTrigger" in self._config["events"] and "subscriptions" in self._config["events"]["gcodeCommandTrigger"]:
|
||||||
|
for trigger in self._config["events"]["gcodeCommandTrigger"]["subscriptions"]:
|
||||||
|
if not ("event" in trigger and "command" in trigger):
|
||||||
|
continue
|
||||||
|
|
||||||
|
newTrigger = {"type": "gcode"}
|
||||||
|
if disableGcodeCommands and not disableAllCommands:
|
||||||
|
newTrigger["enabled"] = False
|
||||||
|
|
||||||
|
newTrigger["event"], newTrigger["command"] = migrateEventHook(trigger["event"], trigger["command"])
|
||||||
|
newTrigger["command"] = newTrigger["command"].split(",")
|
||||||
|
newEvents["subscriptions"].append(newTrigger)
|
||||||
|
|
||||||
|
self._config["events"] = newEvents
|
||||||
|
self.save(force=True)
|
||||||
|
self._logger.info("Migrated %d event subscriptions to new format and structure" % len(newEvents["subscriptions"]))
|
||||||
|
|
||||||
def save(self, force=False):
|
def save(self, force=False):
|
||||||
if not self._dirty and not force:
|
if not self._dirty and not force:
|
||||||
return
|
return
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue