aboutsummaryrefslogtreecommitdiff
path: root/voctocore/lib/controlserver.py
blob: c457fee81ae27180d8ab37b5beb5d48d225540bc (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. command, argstring = (line+' ').split(' ', 1)
  59. args = argstring.strip().split()
  60. # log function-call as parsed
  61. self.log.info("Read Function-Call from %s: %s( %s )", conn.getpeername(), command, args)
  62. # check that the function-call is a known Command
  63. if not hasattr(self.commands, command):
  64. return False, 'unknown command %s' % command
  65. try:
  66. # fetch the function-pointer
  67. f = getattr(self.commands, command)
  68. # call the function
  69. ret = f(*args)
  70. # signal method call to all other connected clients
  71. self.signal(conn, command, args)
  72. # if it returned an iterable, probably (Success, Message), pass that on
  73. if hasattr(ret, '__iter__'):
  74. return ret
  75. else:
  76. # otherwise construct a tuple
  77. return (ret, None)
  78. except Exception as e:
  79. self.log.error("Trapped Exception in Remote-Communication: %s", e)
  80. # In case of an Exception, return that
  81. return False, str(e)
  82. def signal(self, origin_conn, command, args):
  83. for conn in self.currentConnections:
  84. if conn == origin_conn:
  85. continue
  86. self.log.debug(
  87. 'signaling connection %s the successful '
  88. 'execution of the command %s',
  89. conn.getpeername(), command)
  90. conn.makefile('w').write(
  91. "signal %s %s\n" % (command, ' '.join(args))
  92. )