diff --git a/lib/tfw/components/directory_monitor.py b/lib/tfw/components/directory_monitor.py new file mode 100644 index 0000000..5dae10e --- /dev/null +++ b/lib/tfw/components/directory_monitor.py @@ -0,0 +1,35 @@ +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler + +from tfw.networking.event_handlers.server_connector import ServerUplinkConnector +from tfw.util import RateLimiter + +from tfw.config.logs import logging +log = logging.getLogger(__name__) + + + +class WebideReloadEventHandler(FileSystemEventHandler): + def __init__(self): + super().__init__() + self.uplink = ServerUplinkConnector() + + @RateLimiter(rate_per_second=5) + def on_modified(self, event): + log.debug(event) + anchor = 'anchor_webide' + self.uplink.send(anchor, {'anchor': anchor, + 'data': {'command': 'reload'}}) + + +class DirectoryMonitor: + def __init__(self, directory): + self.observer = Observer() + self.observer.schedule(WebideReloadEventHandler(), directory, recursive=True) + + 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 abb5738..5b5de40 100644 --- a/lib/tfw/components/source_code_event_handler.py +++ b/lib/tfw/components/source_code_event_handler.py @@ -3,6 +3,7 @@ from glob import glob from tfw.util import SupervisorMixin from tfw.event_handler_base import EventHandlerBase +from tfw.components.directory_monitor import DirectoryMonitor from tfw.config.logs import logging log = logging.getLogger(__name__) @@ -56,6 +57,9 @@ class SourceCodeEventHandler(EventHandlerBase, SupervisorMixin): 'select': self.select } + self.monitor = DirectoryMonitor(directory) + self.monitor.watch() # This runs on a separate thread + def read(self, data): try: data['content'] = self.filemanager.file_contents except PermissionError: data['content'] = 'You have no permission to open that file :(' @@ -85,6 +89,9 @@ class SourceCodeEventHandler(EventHandlerBase, SupervisorMixin): self.attach_fileinfo(data) return data_json + def cleanup(self): + self.monitor.stop() + def map_file_extension_to_language(filename): language_map = { diff --git a/lib/tfw/event_handler_base.py b/lib/tfw/event_handler_base.py index 4dfdb84..2841e0f 100644 --- a/lib/tfw/event_handler_base.py +++ b/lib/tfw/event_handler_base.py @@ -23,6 +23,9 @@ class EventHandlerBase: def handle_reset(self, data_json): return None + def cleanup(self): + pass + def message_other(self, anchor, data): message = { 'anchor': anchor, diff --git a/lib/tfw/util.py b/lib/tfw/util.py index 4194953..7ee99cc 100644 --- a/lib/tfw/util.py +++ b/lib/tfw/util.py @@ -1,6 +1,8 @@ import xmlrpc.client, zmq from contextlib import suppress from xmlrpc.client import Fault as SupervisorFault +from time import time, sleep +from functools import wraps from tfw.config import tfwenv @@ -31,3 +33,23 @@ class SupervisorMixin: class ZMQConnectorBase: def __init__(self, zmq_context=None): self._zmq_context = zmq_context or zmq.Context.instance() + + +class RateLimiter: + def __init__(self, rate_per_second): + self.min_interval = 1 / float(rate_per_second) + self.last_call = time() + + def __call__(self, fun): + @wraps(fun) + def wrapper(*args, **kwargs): + self._limit_rate() + fun(*args, **kwargs) + return wrapper + + def _limit_rate(self): + since_last_call = time() - self.last_call + to_next_call = self.min_interval - since_last_call + self.last_call = time() + if to_next_call > 0: + sleep(to_next_call) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 5cfef8d..e057890 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ tornado==4.5.3 pyzmq==16.0.4 transitions==0.6.4 terminado==0.8.1 +watchdog==0.8.3 diff --git a/src/demo/event_handler_main.py b/src/demo/event_handler_main.py index f7623a9..dda5c36 100644 --- a/src/demo/event_handler_main.py +++ b/src/demo/event_handler_main.py @@ -6,6 +6,9 @@ from tfw.config import tfwenv if __name__ == '__main__': - anchor_webide = SourceCodeEventHandler('anchor_webide', tfwenv.WEBIDE_WD, 'login') - anchor_terminado = TerminadoEventHandler('anchor_terminado', 'terminado') - IOLoop.instance().start() + eventhandlers = {SourceCodeEventHandler('anchor_webide', tfwenv.WEBIDE_WD, 'login'), + TerminadoEventHandler('anchor_terminado', 'terminado')} + try: + IOLoop.instance().start() + finally: + for eh in eventhandlers: eh.cleanup()