comparison 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
comparison
equal deleted inserted replaced
5:982eeffe9d95 8:9c0435a617db
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3
4 ## @package dbus_vrm
5
6 import logging
7 logger = logging.getLogger(__name__)
8
9 import codecs
10 import threading
11 import subprocess
12 from time import sleep
13
14 # Runs a command, and calls sendfeedback with the statusupdates.
15 class StreamCommand(object):
16 SIGNALS = {
17 1: "SIGHUP", 2: "SIGINT", 3: "SIGQUIT", 4: "SIGILL", 6: "SIGABRT", 7: "SIGBUS", 8: "SIGFPE",
18 9: "SIGKILL", 10: "SIGBUS", 11: "SIGSEGV", 12: "SIGSYS", 13: "SIGPIPE", 14: "SIGALRM",
19 15: "SIGTERM"}
20
21 def run(self, command, timeout, feedbacksender):
22 self.feedbacksender = feedbacksender
23 self.returncode = None
24 self.utf8_decoder = codecs.getdecoder("utf_8")
25 self.latin1_decoder = codecs.getdecoder("latin1")
26
27 def target():
28 logger.info('Thread started for running %s' % command)
29 self.feedbacksender.send({"status": "starting"})
30
31 try:
32 self.process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
33 except OSError as e:
34 logger.info("Command %s could not be started, errno: %s, msg: %s"
35 % (command, e.errno, e.strerror))
36 self.feedbacksender.send({"status": "error",
37 "errormessage": "Could not start (errno %s, msg %s)" % (e.errno, e.strerror),
38 "errorcode": 731}, finished=True)
39
40 self.process = None
41 return
42
43 self.readandsend()
44
45
46 thread = threading.Thread(target=target)
47 thread.start()
48 thread.join(timeout)
49
50 if self.process is None:
51 # Error message has already beent sent
52 return None
53
54 # Make sure to send all the output
55 self.readandsend()
56
57 if thread.is_alive():
58 logger.warning("Command %s will now be terminated because of timeout" % command)
59 self.process.terminate() # TODO or should it be killed?
60 thread.join()
61 logger.warning("Command %s has been terminated" % command)
62 r = {"status": "error", "errormessage": "Stopped by timeout", "errorcode": 732}
63
64 elif self.process.returncode < 0:
65 signal = -1 * self.process.returncode
66 error = "Stopped with signal %d - %s" % (signal, self.SIGNALS.get(signal, "unknown"))
67 logger.warning("Command %s abnormal stop. %s" % (command, error))
68 r = {"status": "error", "errorcode": 733, "errormessage": error}
69
70 else:
71 logger.info("Command %s execution completed. Exitcode %d" % (command, self.process.returncode))
72 r = {"status": "finished", "exitcode": self.process.returncode}
73
74 self.feedbacksender.send(r, finished=True)
75 return self.process.returncode
76
77 def readandsend(self):
78 # TODO: check that below code works OK with vup stdout encoding (UTF-8), including non-standard ASCII chars
79
80 while True:
81 self.process.stdout.flush()
82 line = self.process.stdout.readline()
83 try:
84 unicode_line, _ = self.utf8_decoder(line)
85 except UnicodeDecodeError:
86 unicode_line, _ = self.latin1_decoder(line)
87
88 # Max length on pubnub is 1800 chars, and output is much better readable with the bare eye
89 # when sent per line. So no need to send it alltogether.
90 self.feedbacksender.send({"status": "running", "xmloutput": unicode_line})
91 if line == b'' and self.process.poll() != None:
92 break
93 sleep(0.04)