diff options
Diffstat (limited to 'voctocore/lib/controlserver.py')
-rw-r--r-- | voctocore/lib/controlserver.py | 118 |
1 files changed, 81 insertions, 37 deletions
diff --git a/voctocore/lib/controlserver.py b/voctocore/lib/controlserver.py index fffeda8..e267ca7 100644 --- a/voctocore/lib/controlserver.py +++ b/voctocore/lib/controlserver.py @@ -1,65 +1,109 @@ -import socket, threading, queue, logging +#!/usr/bin/python3 +import socket, logging from gi.repository import GObject -def controlServerEntrypoint(f): - # mark the method as something that requires view's class - f.is_control_server_entrypoint = True - return f +from lib.commands import ControlServerCommands class ControlServer(): log = logging.getLogger('ControlServer') - def __init__(self, videomix): + + boundSocket = None + + def __init__(self, pipeline): '''Initialize server and start listening.''' - self.videomix = videomix + self.commands = ControlServerCommands(pipeline) - sock = socket.socket() - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.bind(('0.0.0.0', 23000)) - sock.listen(1) + port = 9999 + self.log.debug('Binding to Command-Socket on [::]:%u', port) + self.boundSocket = socket.socket(socket.AF_INET6) + self.boundSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.boundSocket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False) + self.boundSocket.bind(('::', port)) + self.boundSocket.listen(1) - # register socket for callback inside the GTK-Mainloop - GObject.io_add_watch(sock, GObject.IO_IN, self.listener) + self.log.debug('Setting GObject io-watch on Socket') + GObject.io_add_watch(self.boundSocket, GObject.IO_IN, self.on_connect) - def listener(self, sock, *args): + def on_connect(self, sock, *args): '''Asynchronous connection listener. Starts a handler for each connection.''' conn, addr = sock.accept() - self.log.info("Connection from %s", addr) + self.log.info("Incomming Connection from %s", addr) - # register data-received handler inside the GTK-Mainloop - GObject.io_add_watch(conn, GObject.IO_IN, self.handler) + self.log.debug('Setting GObject io-watch on Connection') + GObject.io_add_watch(conn, GObject.IO_IN, self.on_data) return True - def handler(self, conn, *args): + def on_data(self, conn, *args): '''Asynchronous connection handler. Processes each line from the socket.''' - line = conn.recv(4096) - if not len(line): - self.log.debug("Connection closed.") + # construct a file-like object fro mthe socket + # to be able to read linewise and in utf-8 + filelike = conn.makefile('rw') + + # read a line from the socket + line = filelike.readline().strip() + + # no data = remote closed connection + if len(line) == 0: + self.log.info("Connection closed.") return False - r = self.processLine(line.decode('utf-8')) - if isinstance(r, str): - conn.send((r+'\n').encode('utf-8')) + # 'quit' = remote wants us to close the connection + if line == 'quit': + self.log.info("Client asked us to close the Connection") return False - conn.send('OK\n'.encode('utf-8')) + # process the received line + success, msg = self.processLine(line) + + # success = False -> error + if success == False: + # on error-responses the message is mandatory + if msg is None: + msg = '<no message>' + + # respond with 'error' and the message + filelike.write('error '+msg+'\n') + self.log.info("Function-Call returned an Error: %s", msg) + + # keep on listening on that connection + return True + + # success = True and not message + if msg is None: + # respond with a simple 'ok' + filelike.write('ok\n') + else: + # respond with the returned message + filelike.write('ok '+msg+'\n') return True - - - def processLine(self, line): - command, argstring = (line.strip()+' ').split(' ', 1) + # split line into command and optional args + command, argstring = (line+' ').split(' ', 1) args = argstring.strip().split() - self.log.info(command % args) - if not hasattr(self.videomix, command): - return 'unknown command {}'.format(command) + # log function-call as parsed + self.log.info("Read Function-Call from Socket: %s( %s )", command, args) + + # check that the function-call is a known Command + if not hasattr(self.commands, command): + return False, 'unknown command %s' % command - f = getattr(self.videomix, command) - if not hasattr(f, 'is_control_server_entrypoint'): - return 'method {} not callable from controlserver'.format(command) try: - return f(*args) + # fetch the function-pointer + f = getattr(self.commands, command) + + # call the function + ret = f(*args) + + # if it returned an iterable, probably (Success, Message), pass that on + if hasattr(ret, '__iter__'): + return ret + else: + # otherwise construct a tuple + return (ret, None) + except Exception as e: - return str(e) + # In case of an Exception, return that + return False, str(e) |