diff velib_python/streamcommand.py @ 8:9c0435a617db

Import velib_python
author Daniel O'Connor <darius@dons.net.au>
date Sun, 05 Dec 2021 14:35:36 +1030
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/velib_python/streamcommand.py	Sun Dec 05 14:35:36 2021 +1030
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+## @package dbus_vrm
+
+import logging
+logger = logging.getLogger(__name__)
+
+import codecs
+import threading
+import subprocess
+from time import sleep
+
+# Runs a command, and calls sendfeedback with the statusupdates.
+class StreamCommand(object):
+	SIGNALS = {
+		1: "SIGHUP", 2: "SIGINT", 3: "SIGQUIT", 4: "SIGILL", 6: "SIGABRT", 7: "SIGBUS", 8: "SIGFPE",
+		9: "SIGKILL", 10: "SIGBUS", 11: "SIGSEGV", 12: "SIGSYS", 13: "SIGPIPE", 14: "SIGALRM",
+		15: "SIGTERM"}
+
+	def run(self, command, timeout, feedbacksender):
+		self.feedbacksender = feedbacksender
+		self.returncode = None
+		self.utf8_decoder = codecs.getdecoder("utf_8")
+		self.latin1_decoder = codecs.getdecoder("latin1")
+
+		def target():
+			logger.info('Thread started for running %s' % command)
+			self.feedbacksender.send({"status": "starting"})
+
+			try:
+				self.process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
+			except OSError as e:
+				logger.info("Command %s could not be started, errno: %s, msg: %s"
+					% (command, e.errno, e.strerror))
+				self.feedbacksender.send({"status": "error",
+					"errormessage": "Could not start (errno %s, msg %s)" % (e.errno, e.strerror),
+					"errorcode": 731}, finished=True)
+
+				self.process = None
+				return
+
+			self.readandsend()
+
+
+		thread = threading.Thread(target=target)
+		thread.start()
+		thread.join(timeout)
+
+		if self.process is None:
+			# Error message has already beent sent
+			return None
+
+		# Make sure to send all the output
+		self.readandsend()
+
+		if thread.is_alive():
+			logger.warning("Command %s will now be terminated because of timeout" % command)
+			self.process.terminate()  # TODO or should it be killed?
+			thread.join()
+			logger.warning("Command %s has been terminated" % command)
+			r = {"status": "error", "errormessage": "Stopped by timeout", "errorcode": 732}
+
+		elif self.process.returncode < 0:
+			signal = -1 * self.process.returncode
+			error = "Stopped with signal %d - %s" % (signal, self.SIGNALS.get(signal, "unknown"))
+			logger.warning("Command %s abnormal stop. %s" % (command, error))
+			r = {"status": "error", "errorcode": 733, "errormessage": error}
+
+		else:
+			logger.info("Command %s execution completed. Exitcode %d" % (command, self.process.returncode))
+			r = {"status": "finished", "exitcode": self.process.returncode}
+
+		self.feedbacksender.send(r, finished=True)
+		return self.process.returncode
+
+	def readandsend(self):
+		# TODO: check that below code works OK with vup stdout encoding (UTF-8), including non-standard ASCII chars
+
+		while True:
+			self.process.stdout.flush()
+			line = self.process.stdout.readline()
+			try:
+				unicode_line, _ = self.utf8_decoder(line)
+			except UnicodeDecodeError:
+				unicode_line, _ = self.latin1_decoder(line)
+
+			# Max length on pubnub is 1800 chars, and output is much better readable with the bare eye
+			# when sent per line. So no need to send it alltogether.
+			self.feedbacksender.send({"status": "running", "xmloutput": unicode_line})
+			if line == b'' and self.process.poll() != None:
+				break
+			sleep(0.04)