Skip to content

Commit

Permalink
Added automatic migration of old event subscription format to the new…
Browse files Browse the repository at this point in the history
… one

(cherry picked from commit e7088ef)
  • Loading branch information
foosel committed Aug 31, 2014
1 parent 590d2f2 commit 18952c5
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 59 deletions.
101 changes: 53 additions & 48 deletions src/octoprint/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,36 +203,42 @@ def eventCallback(self, event, payload):


class CommandTrigger(GenericEventListener):
def __init__(self, triggerType, printer):
def __init__(self, printer):
GenericEventListener.__init__(self)
self._printer = printer
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
respective commands.
"""
if not settings().get(["events", triggerType]):
if not settings().get(["events"]):
return

if not settings().getBoolean(["events", triggerType, "enabled"]):
if not settings().getBoolean(["events", "enabled"]):
return

eventsToSubscribe = []
for subscription in settings().get(["events", triggerType, "subscriptions"]):
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))
for subscription in settings().get(["events", "subscriptions"]):
if not "event" in subscription.keys() or not "command" in subscription.keys() \
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

event = subscription["event"]
command = subscription["command"]
commandType = subscription["type"]

if not event in self._subscriptions.keys():
self._subscriptions[event] = []
self._subscriptions[event].append(command)
self._subscriptions[event].append((command, commandType))

if not event in eventsToSubscribe:
eventsToSubscribe.append(event)
Expand All @@ -250,18 +256,48 @@ def eventCallback(self, event, payload):
if not event in self._subscriptions:
return

for command in self._subscriptions[event]:
for command, commandType in self._subscriptions[event]:
try:
processedCommand = self._processCommand(command, payload)
self.executeCommand(processedCommand)
if isinstance(command, (tuple, list, set)):
processedCommand = []
for c in command:
processedCommand.append(self._processCommand(c, payload))
else:
processedCommand = self._processCommand(command, payload)
self.executeCommand(processedCommand, commandType)
except KeyError, e:
self._logger.warn("There was an error processing one or more placeholders in the following command: %s" % command)

def executeCommand(self, command):
"""
Not implemented, override in child classes
"""
pass
def executeCommand(self, command, commandType):
if commandType == "system":
self._executeSystemCommand(command)
elif commandType == "gcode":
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):
"""
Expand Down Expand Up @@ -302,34 +338,3 @@ def _processCommand(self, command, payload):
params.update(payload)

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(","))
5 changes: 2 additions & 3 deletions src/octoprint/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,8 @@ def run(self):
# configure timelapse
octoprint.timelapse.configureTimelapse()

# setup system and gcode command triggers
events.SystemCommandTrigger(printer)
events.GcodeCommandTrigger(printer)
# setup command triggers
events.CommandTrigger(printer)
if self._debug:
events.DebugEventListener()

Expand Down
103 changes: 95 additions & 8 deletions src/octoprint/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,8 @@ def settings(init=False, configfile=None, basedir=None):
"config": "/default/path/to/your/cura/config.ini"
},
"events": {
"systemCommandTrigger": {
"enabled": False
},
"gcodeCommandTrigger": {
"enabled": False
}
"enabled": False,
"subscriptions": []
},
"api": {
"enabled": False,
Expand Down Expand Up @@ -173,7 +169,7 @@ def __init__(self, configfile=None, basedir=None):
self._configfile = configfile
else:
self._configfile = os.path.join(self.settings_dir, "config.yaml")
self.load()
self.load(migrate=True)

def _init_settings_dir(self, basedir):
if basedir is not None:
Expand All @@ -189,14 +185,105 @@ def _getDefaultFolder(self, type):

#~~ load and save

def load(self):
def load(self, migrate=False):
if os.path.exists(self._configfile) and os.path.isfile(self._configfile):
with open(self._configfile, "r") as f:
self._config = yaml.safe_load(f)
# chamged from else to handle cases where the file exists, but is empty / 0 bytes
if not 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):
if not self._dirty and not force:
return
Expand Down

0 comments on commit 18952c5

Please sign in to comment.