aboutsummaryrefslogtreecommitdiff
path: root/voctocore/lib/controlserver.py
blob: 8ebb11b651b70e0554369fcc0ec7ee0ce664609a (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. log = logging.getLogger('ControlServer')
  8. boundSocket = None
  9. def __init__(self, pipeline):
  10. '''Initialize server and start listening.'''
  11. super().__init__(port=9999)
  12. self.commands = ControlServerCommands(pipeline)
  13. def on_accepted(self, conn, addr):
  14. '''Asynchronous connection listener. Starts a handler for each connection.'''
  15. self.log.debug('Setting GObject io-watch on Connection')
  16. GObject.io_add_watch(conn, GObject.IO_IN, self.on_data)
  17. def on_data(self, conn, *args):
  18. '''Asynchronous connection handler. Processes each line from the socket.'''
  19. # construct a file-like object fro mthe socket
  20. # to be able to read linewise and in utf-8
  21. filelike = conn.makefile('rw')
  22. # read a line from the socket
  23. line = ''
  24. try:
  25. line = filelike.readline().strip()
  26. except Exception as e:
  27. self.log.warn("Can't read from socket: %s", e)
  28. # no data = remote closed connection
  29. if len(line) == 0:
  30. self.close_connection(conn)
  31. return False
  32. # 'quit' = remote wants us to close the connection
  33. if line == 'quit':
  34. self.log.info("Client asked us to close the Connection")
  35. self.close_connection(conn)
  36. return False
  37. # process the received line
  38. success, msg = self.processLine(conn, line)
  39. # success = False -> error
  40. if success == False:
  41. # on error-responses the message is mandatory
  42. if msg is None:
  43. msg = '<no message>'
  44. # respond with 'error' and the message
  45. filelike.write('error '+msg+'\n')
  46. self.log.info("Function-Call returned an Error: %s", msg)
  47. # keep on listening on that connection
  48. return True
  49. # success = True and not message
  50. if msg is None:
  51. # respond with a simple 'ok'
  52. filelike.write('ok\n')
  53. else:
  54. # respond with the returned message
  55. filelike.write('ok '+msg+'\n')
  56. return True
  57. def processLine(self, conn, line):
  58. # split line into command and optional args
  59. command, argstring = (line+' ').split(' ', 1)
  60. args = argstring.strip().split()
  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. self.signal(conn, command, args)
  73. # if it returned an iterable, probably (Success, Message), pass that on
  74. if hasattr(ret, '__iter__'):
  75. return ret
  76. else:
  77. # otherwise construct a tuple
  78. return (ret, None)
  79. except Exception as e:
  80. self.log.error("Trapped Exception in Remote-Communication: %s", e)
  81. # In case of an Exception, return that
  82. return False, str(e)
  83. def signal(self, origin_conn, command, args):
  84. for conn in self.currentConnections:
  85. if conn == origin_conn:
  86. continue
  87. self.log.debug(
  88. 'signaling connection %s the successful '
  89. 'execution of the command %s',
  90. conn.getpeername(), command)
  91. conn.makefile('w').write(
  92. "signal %s %s\n" % (command, ' '.join(args))
  93. )