Merge pull request #17 from avatao-content/terminado_commands

Implement terminal manipulation via a new EventHandler
This commit is contained in:
Bokros Bálint 2018-03-07 11:43:02 +01:00 committed by GitHub
commit 6db4c80671
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 131 additions and 25 deletions

View File

@ -60,7 +60,18 @@ ENV PYTHONPATH="/usr/local/lib/" \
TFW_LIB_DIR="/usr/local/lib/" \ TFW_LIB_DIR="/usr/local/lib/" \
TFW_CONTROLLER_DIR="/srv/controller" \ TFW_CONTROLLER_DIR="/srv/controller" \
TFW_TERMINADO_DIR="/tmp/terminado_server" \ TFW_TERMINADO_DIR="/tmp/terminado_server" \
TFW_FRONTEND_DIR="/srv/frontend" TFW_FRONTEND_DIR="/srv/frontend" \
TFW_HISTFILE="/home/${AVATAO_USER}/.bash_history" \
PROMPT_COMMAND="history -a"
RUN echo "shopt -s cmdhist\n" \
"shopt -s histappend\n" \
"unset HISTCONTROL\n" \
"export HISTFILE=$TFW_HISTFILE\n" \
"export HISTFILESIZE=1000\n" \
"export HISTSIZE=1000\n" \
'PROMPT_COMMAND="history -a"\n' \
>> /home/${AVATAO_USER}/.bashrc
COPY nginx/nginx.conf ${TFW_NGINX_CONF} COPY nginx/nginx.conf ${TFW_NGINX_CONF}
COPY nginx/components/ ${TFW_NGINX_COMPONENTS} COPY nginx/components/ ${TFW_NGINX_COMPONENTS}
@ -74,7 +85,6 @@ COPY supervisor/supervisord.conf ${TFW_SUPERVISORD_CONF}
COPY supervisor/components/ ${TFW_SUPERVISORD_COMPONENTS} COPY supervisor/components/ ${TFW_SUPERVISORD_COMPONENTS}
COPY lib ${TFW_LIB_DIR} COPY lib ${TFW_LIB_DIR}
COPY src/controller ${TFW_CONTROLLER_DIR} COPY src/controller ${TFW_CONTROLLER_DIR}
COPY lib/tfw/components/terminado_mini_server.py ${TFW_TERMINADO_DIR}/
### TFW internals ^ ### DEMO v ############################################################### ### TFW internals ^ ### DEMO v ###############################################################

View File

@ -13,6 +13,7 @@ class WebideReloadEventHandler(FileSystemEventHandler):
super().__init__() super().__init__()
self.uplink = ServerUplinkConnector() self.uplink = ServerUplinkConnector()
self._paused = False self._paused = False
self.ignore = 0
def pause(self): def pause(self):
self._paused = True self._paused = True
@ -23,6 +24,9 @@ class WebideReloadEventHandler(FileSystemEventHandler):
@RateLimiter(rate_per_second=5) @RateLimiter(rate_per_second=5)
def on_modified(self, event): def on_modified(self, event):
if self._paused: return if self._paused: return
if self.ignore > 0:
self.ignore = self.ignore - 1
return
log.debug(event) log.debug(event)
key = 'webide' key = 'webide'
self.uplink.send(key, {'data': {'command': 'reload'}}) self.uplink.send(key, {'data': {'command': 'reload'}})
@ -42,6 +46,14 @@ class DirectoryMonitor:
self.observer.stop() self.observer.stop()
self.observer.join() self.observer.join()
@property
def ignore(self):
return self.eventhandler.ignore
@ignore.setter
def ignore(self, value):
self.ignore = value if value >= 0 else 0
@property @property
def pauser(self): def pauser(self):
return DirectoryMonitor.Pauser(self) return DirectoryMonitor.Pauser(self)

View File

@ -0,0 +1,51 @@
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
from os.path import dirname
class CallbackEventHandler(PatternMatchingEventHandler):
def __init__(self, files, *callbacks):
super().__init__(files)
self.callbacks = callbacks
def on_modified(self, event):
for callback in self.callbacks:
callback()
class HistoryMonitor:
def __init__(self, histfile):
self.histfile = histfile
self._history = []
self._last_length = len(self._history)
self._callbacks = []
self.observer = Observer()
self.observer.schedule(CallbackEventHandler([self.histfile],
self._fetch_history,
self._invoke_callbacks),
dirname(self.histfile))
@property
def history(self):
return self._history
@property
def callbacks(self):
return self._callbacks
def _fetch_history(self):
self._last_length = len(self._history)
with open(self.histfile, 'r') as ifile:
self._history = [line.rstrip() for line in ifile.readlines()]
def _invoke_callbacks(self):
if self._last_length < len(self._history):
for callback in self.callbacks:
callback(self.history)
def watch(self):
self.observer.start()
def stop(self):
self.observer.stop()
self.observer.join()

View File

@ -68,9 +68,9 @@ class SourceCodeEventHandler(TriggerlessEventHandler):
return data return data
def write(self, data): def write(self, data):
with self.monitor.pauser: self.monitor.eventhandler.ignore = 1
try: self.filemanager.file_contents = data['content'] try: self.filemanager.file_contents = data['content']
except Exception: log.exception('Error writing file!') except Exception: log.exception('Error writing file!')
del data['content'] del data['content']
return data return data

View File

@ -1,17 +1,38 @@
from tfw.components.terminado_mini_server import TerminadoMiniServer
from tfw.components.history_monitor import HistoryMonitor
from tfw.event_handler_base import TriggerlessEventHandler from tfw.event_handler_base import TriggerlessEventHandler
from tfw.components.mixins import SupervisorMixin
from tfw.config import tfwenv from tfw.config import tfwenv
from tfw.config.logs import logging from tfw.config.logs import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class TerminadoEventHandler(TriggerlessEventHandler, SupervisorMixin): class TerminadoEventHandler(TriggerlessEventHandler):
def __init__(self, key, process_name): def __init__(self, key):
super().__init__(key) super().__init__(key)
self.working_directory = tfwenv.TERMINADO_DIR self.working_directory = tfwenv.TERMINADO_DIR
self.process_name = process_name self._historymonitor = HistoryMonitor(tfwenv.HISTFILE)
self.start_process(self.process_name) self.terminado_server = TerminadoMiniServer('/terminal', tfwenv.TERMINADO_PORT, tfwenv.TERMINADO_WD, ['bash'])
self.commands = {'write': self.write,
'read': self.read}
self._historymonitor.watch()
self.terminado_server.listen()
@property
def historymonitor(self):
return self._historymonitor
def handle_event(self, key, data_json): def handle_event(self, key, data_json):
log.debug('TerminadoEventHandler received event for key {}'.format(key)) log.debug('TerminadoEventHandler received event: {}'.format(data_json))
# TODO: wat do? data_json['data'] = self.commands[data_json['data']['command']](data_json['data'])
return data_json
def write(self, data):
self.terminado_server.pty.write(data['shellcmd'])
def read(self, data):
data['count'] = int(data.get('count', 1))
data['history'] = self.historymonitor.history[-data['count']:]
return data
def cleanup(self):
self.historymonitor.stop()

View File

@ -1,6 +1,6 @@
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
from tornado.web import Application from tornado.web import Application
from terminado import TermSocket, UniqueTermManager from terminado import TermSocket, SingleTermManager
from tfw.config import tfwenv from tfw.config import tfwenv
from tfw.config.logs import logging from tfw.config.logs import logging
@ -10,24 +10,37 @@ log = logging.getLogger(__name__)
class TerminadoMiniServer: class TerminadoMiniServer:
def __init__(self, url, port, workdir, shellcmd): def __init__(self, url, port, workdir, shellcmd):
self.port = port self.port = port
self._term_manager = SingleTermManager(shell_command=shellcmd,
term_settings={'cwd': workdir})
self.application = Application( self.application = Application(
[( [(
url, url,
TerminadoMiniServer.CORSTermSocket, TerminadoMiniServer.ResetterTermSocket,
{'term_manager': UniqueTermManager(shell_command=shellcmd, {'term_manager': self._term_manager}
term_settings={'cwd': workdir})}
)] )]
) )
class CORSTermSocket(TermSocket): @property
def term_manager(self):
return self._term_manager
@property
def pty(self):
return self.term_manager.terminal.ptyproc
class ResetterTermSocket(TermSocket):
def check_origin(self, origin): def check_origin(self, origin):
return True return True
def on_close(self):
self.term_manager.terminal = None
self.term_manager.get_terminal()
def listen(self): def listen(self):
self.application.listen(self.port) self.application.listen(self.port)
IOLoop.instance().start()
if __name__ == '__main__': if __name__ == '__main__':
log.info('Terminado Mini Server listening on {}'.format(tfwenv.TERMINADO_PORT)) log.info('Terminado Mini Server listening on {}'.format(tfwenv.TERMINADO_PORT))
TerminadoMiniServer('/terminal', tfwenv.TERMINADO_PORT, tfwenv.TERMINADO_WD, ['bash']).listen() TerminadoMiniServer('/terminal', tfwenv.TERMINADO_PORT, tfwenv.TERMINADO_WD, ['bash']).listen()
IOLoop.instance().start()

View File

@ -1,14 +1,17 @@
from tornado.ioloop import IOLoop
from tfw.components.source_code_event_handler import SourceCodeEventHandler from tfw.components.source_code_event_handler import SourceCodeEventHandler
from tfw.components.terminado_event_handler import TerminadoEventHandler from tfw.components.terminado_event_handler import TerminadoEventHandler
from tfw.components.process_managing_event_handler import ProcessManagingEventHandler from tfw.components.process_managing_event_handler import ProcessManagingEventHandler
from tornado.ioloop import IOLoop
from tfw.config import tfwenv from tfw.config import tfwenv
from tfw.config.logs import logging
log = logging.getLogger(__name__)
if __name__ == '__main__': if __name__ == '__main__':
ide = SourceCodeEventHandler('webide', tfwenv.WEBIDE_WD) ide = SourceCodeEventHandler('webide', tfwenv.WEBIDE_WD)
terminado = TerminadoEventHandler('terminado', 'terminado') terminado = TerminadoEventHandler('shell')
terminado.historymonitor.callbacks.append(lambda hist: log.debug('User executed command: "{}"'.format(hist[-1])))
processmanager = ProcessManagingEventHandler('processmanager', ide.monitor) processmanager = ProcessManagingEventHandler('processmanager', ide.monitor)
eventhandlers = {ide, terminado, processmanager} eventhandlers = {ide, terminado, processmanager}

View File

@ -1,4 +0,0 @@
[program:terminado]
directory=%(ENV_TFW_TERMINADO_DIR)s
command=env python terminado_mini_server.py
autostart=false