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