summaryrefslogtreecommitdiff
path: root/voctocore/lib/controlserver.py
blob: 79aa617c01ec396af6d7170563484ecffbd9c8b4 (plain)
  1. #!/usr/bin/python3
  2. import socket, logging, traceback
  3. from gi.repository import GObject
  4. from lib.commands import ControlServerCommands
  5. class ControlServer():
  6. log = logging.getLogger('ControlServer')
  7. boundSocket = None
  8. def __init__(self, pipeline):
  9. '''Initialize server and start listening.'''
  10. self.commands = ControlServerCommands(pipeline)
  11. port = 9999
  12. self.log.debug('Binding to Command-Socket on [::]:%u', port)
  13. self.boundSocket = socket.socket(socket.AF_INET6)
  14. self.boundSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  15. self.boundSocket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
  16. self.boundSocket.bind(('::', port))
  17. self.boundSocket.listen(1)
  18. self.log.debug('Setting GObject io-watch on Socket')
  19. GObject.io_add_watch(self.boundSocket, GObject.IO_IN, self.on_connect)
  20. def on_connect(self, sock, *args):
  21. '''Asynchronous connection listener. Starts a handler for each connection.'''
  22. conn, addr = sock.accept()
  23. self.log.info("Incomming Connection from %s", addr)
  24. self.log.debug('Setting GObject io-watch on Connection')
  25. GObject.io_add_watch(conn, GObject.IO_IN, self.on_data)
  26. return True
  27. def on_data(self, conn, *args):
  28. '''Asynchronous connection handler. Processes each line from the socket.'''
  29. # construct a file-like object fro mthe socket
  30. # to be able to read linewise and in utf-8
  31. filelike = conn.makefile('rw')
  32. # read a line from the socket
  33. line = filelike.readline().strip()
  34. # no data = remote closed connection
  35. if len(line) == 0:
  36. self.log.info("Connection closed.")
  37. return False
  38. # 'quit' = remote wants us to close the connection
  39. if line == 'quit':
  40. self.log.info("Client asked us to close the Connection")
  41. return False
  42. # process the received line
  43. success, msg = self.processLine(line)
  44. # success = False -> error
  45. if success == False:
  46. # on error-responses the message is mandatory
  47. if msg is None:
  48. msg = '<no message>'
  49. # respond with 'error' and the message
  50. filelike.write('error '+msg+'\n')
  51. self.log.info("Function-Call returned an Error: %s", msg)
  52. # keep on listening on that connection
  53. return True
  54. # success = True and not message
  55. if msg is None:
  56. # respond with a simple 'ok'
  57. filelike.write('ok\n')
  58. else:
  59. # respond with the returned message
  60. filelike.write('ok '+msg+'\n')
  61. return True
  62. def processLine(self, line):
  63. # split line into command and optional args
  64. command, argstring = (line+' ').split(' ', 1)
  65. args = argstring.strip().split()
  66. # log function-call as parsed
  67. self.log.info("Read Function-Call from Socket: %s( %s )", command, args)
  68. # check that the function-call is a known Command
  69. if not hasattr(self.commands, command):
  70. return False, 'unknown command %s' % command
  71. try:
  72. # fetch the function-pointer
  73. f = getattr(self.commands, command)
  74. # call the function
  75. ret = f(*args)
  76. # if it returned an iterable, probably (Success, Message), pass that on
  77. if hasattr(ret, '__iter__'):
  78. return ret
  79. else:
  80. # otherwise construct a tuple
  81. return (ret, None)
  82. except Exception as e:
  83. self.log.error("Trapped Exception in Remote-Communication: %s", e)
  84. traceback.print_exc()
  85. # In case of an Exception, return that
  86. return False, str(e)