diff --git a/Dockerfile b/Dockerfile index 4147105..cd0ce9f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,7 +60,18 @@ ENV PYTHONPATH="/usr/local/lib/" \ TFW_LIB_DIR="/usr/local/lib/" \ TFW_CONTROLLER_DIR="/srv/controller" \ 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/components/ ${TFW_NGINX_COMPONENTS} @@ -74,7 +85,6 @@ COPY supervisor/supervisord.conf ${TFW_SUPERVISORD_CONF} COPY supervisor/components/ ${TFW_SUPERVISORD_COMPONENTS} COPY lib ${TFW_LIB_DIR} COPY src/controller ${TFW_CONTROLLER_DIR} -COPY lib/tfw/components/terminado_mini_server.py ${TFW_TERMINADO_DIR}/ ### TFW internals ^ ### DEMO v ############################################################### diff --git a/lib/tfw/components/directory_monitor.py b/lib/tfw/components/directory_monitor.py index 6d2d264..1493133 100644 --- a/lib/tfw/components/directory_monitor.py +++ b/lib/tfw/components/directory_monitor.py @@ -13,6 +13,7 @@ class WebideReloadEventHandler(FileSystemEventHandler): super().__init__() self.uplink = ServerUplinkConnector() self._paused = False + self.ignore = 0 def pause(self): self._paused = True @@ -23,6 +24,9 @@ class WebideReloadEventHandler(FileSystemEventHandler): @RateLimiter(rate_per_second=5) def on_modified(self, event): if self._paused: return + if self.ignore > 0: + self.ignore = self.ignore - 1 + return log.debug(event) key = 'webide' self.uplink.send(key, {'data': {'command': 'reload'}}) @@ -42,6 +46,14 @@ class DirectoryMonitor: self.observer.stop() 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 def pauser(self): return DirectoryMonitor.Pauser(self) diff --git a/lib/tfw/components/history_monitor.py b/lib/tfw/components/history_monitor.py new file mode 100644 index 0000000..f07a57b --- /dev/null +++ b/lib/tfw/components/history_monitor.py @@ -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() diff --git a/lib/tfw/components/source_code_event_handler.py b/lib/tfw/components/source_code_event_handler.py index 19896d4..dd26072 100644 --- a/lib/tfw/components/source_code_event_handler.py +++ b/lib/tfw/components/source_code_event_handler.py @@ -68,9 +68,9 @@ class SourceCodeEventHandler(TriggerlessEventHandler): return data def write(self, data): - with self.monitor.pauser: - try: self.filemanager.file_contents = data['content'] - except Exception: log.exception('Error writing file!') + self.monitor.eventhandler.ignore = 1 + try: self.filemanager.file_contents = data['content'] + except Exception: log.exception('Error writing file!') del data['content'] return data diff --git a/lib/tfw/components/terminado_event_handler.py b/lib/tfw/components/terminado_event_handler.py index 5329e3c..4a8f98a 100644 --- a/lib/tfw/components/terminado_event_handler.py +++ b/lib/tfw/components/terminado_event_handler.py @@ -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.components.mixins import SupervisorMixin from tfw.config import tfwenv from tfw.config.logs import logging log = logging.getLogger(__name__) -class TerminadoEventHandler(TriggerlessEventHandler, SupervisorMixin): - def __init__(self, key, process_name): +class TerminadoEventHandler(TriggerlessEventHandler): + def __init__(self, key): super().__init__(key) self.working_directory = tfwenv.TERMINADO_DIR - self.process_name = process_name - self.start_process(self.process_name) + self._historymonitor = HistoryMonitor(tfwenv.HISTFILE) + 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): - log.debug('TerminadoEventHandler received event for key {}'.format(key)) - # TODO: wat do? + log.debug('TerminadoEventHandler received event: {}'.format(data_json)) + 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() diff --git a/lib/tfw/components/terminado_mini_server.py b/lib/tfw/components/terminado_mini_server.py index 32576cc..153a382 100644 --- a/lib/tfw/components/terminado_mini_server.py +++ b/lib/tfw/components/terminado_mini_server.py @@ -1,6 +1,6 @@ from tornado.ioloop import IOLoop from tornado.web import Application -from terminado import TermSocket, UniqueTermManager +from terminado import TermSocket, SingleTermManager from tfw.config import tfwenv from tfw.config.logs import logging @@ -10,24 +10,37 @@ log = logging.getLogger(__name__) class TerminadoMiniServer: def __init__(self, url, port, workdir, shellcmd): self.port = port + self._term_manager = SingleTermManager(shell_command=shellcmd, + term_settings={'cwd': workdir}) self.application = Application( [( url, - TerminadoMiniServer.CORSTermSocket, - {'term_manager': UniqueTermManager(shell_command=shellcmd, - term_settings={'cwd': workdir})} + TerminadoMiniServer.ResetterTermSocket, + {'term_manager': self._term_manager} )] ) - 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): return True + def on_close(self): + self.term_manager.terminal = None + self.term_manager.get_terminal() + def listen(self): self.application.listen(self.port) - IOLoop.instance().start() if __name__ == '__main__': log.info('Terminado Mini Server listening on {}'.format(tfwenv.TERMINADO_PORT)) TerminadoMiniServer('/terminal', tfwenv.TERMINADO_PORT, tfwenv.TERMINADO_WD, ['bash']).listen() + IOLoop.instance().start() diff --git a/src/demo/event_handler_main.py b/src/demo/event_handler_main.py index dfbd5bb..8500d09 100644 --- a/src/demo/event_handler_main.py +++ b/src/demo/event_handler_main.py @@ -1,14 +1,17 @@ +from tornado.ioloop import IOLoop + from tfw.components.source_code_event_handler import SourceCodeEventHandler from tfw.components.terminado_event_handler import TerminadoEventHandler from tfw.components.process_managing_event_handler import ProcessManagingEventHandler -from tornado.ioloop import IOLoop - from tfw.config import tfwenv +from tfw.config.logs import logging +log = logging.getLogger(__name__) if __name__ == '__main__': 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) eventhandlers = {ide, terminado, processmanager} diff --git a/supervisor/components/terminado.conf b/supervisor/components/terminado.conf deleted file mode 100644 index 30c441d..0000000 --- a/supervisor/components/terminado.conf +++ /dev/null @@ -1,4 +0,0 @@ -[program:terminado] -directory=%(ENV_TFW_TERMINADO_DIR)s -command=env python terminado_mini_server.py -autostart=false