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_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 ###############################################################

View File

@ -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)

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
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

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.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()

View File

@ -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()

View File

@ -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}

View File

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