- #!/usr/bin/python3
- import socket, logging, traceback
- from gi.repository import GObject
- from lib.commands import ControlServerCommands
- from lib.tcpmulticonnection import TCPMultiConnection
- class ControlServer(TCPMultiConnection):
- def __init__(self, pipeline):
- '''Initialize server and start listening.'''
- self.log = logging.getLogger('ControlServer')
- super().__init__(port=9999)
- self.commands = ControlServerCommands(pipeline)
- def on_accepted(self, conn, addr):
- '''Asynchronous connection listener. Starts a handler for each connection.'''
- self.log.debug('Setting GObject io-watch on Connection')
- GObject.io_add_watch(conn, GObject.IO_IN, self.on_data)
- def on_data(self, conn, *args):
- '''Asynchronous connection handler. Processes each line from the socket.'''
- # 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 = ''
- try:
- line = filelike.readline().strip()
- except Exception as e:
- self.log.warn("Can't read from socket: %s", e)
- # no data = remote closed connection
- if len(line) == 0:
- self.close_connection(conn)
- return False
- # 'quit' = remote wants us to close the connection
- if line == 'quit':
- self.log.info("Client asked us to close the Connection")
- self.close_connection(conn)
- return False
- # process the received line
- success, msg = self.processLine(conn, 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, conn, line):
- # split line into command and optional args
- command, argstring = (line+' ').split(' ', 1)
- args = argstring.strip().split()
- # log function-call as parsed
- self.log.info("Read Function-Call from %s: %s( %s )", conn.getpeername(), command, args)
- # check that the function-call is a known Command
- if not hasattr(self.commands, command):
- return False, 'unknown command %s' % command
- try:
- # fetch the function-pointer
- f = getattr(self.commands, command)
- # call the function
- ret = f(*args)
- # signal method call to all other connected clients
- self.signal(conn, command, 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:
- self.log.error("Trapped Exception in Remote-Communication: %s", e)
- # In case of an Exception, return that
- return False, str(e)
- def signal(self, origin_conn, command, args):
- for conn in self.currentConnections:
- if conn == origin_conn:
- continue
- self.log.debug(
- 'signaling connection %s the successful '
- 'execution of the command %s',
- conn.getpeername(), command)
- conn.makefile('w').write(
- "signal %s %s\n" % (command, ' '.join(args))
- )
|