mirror of
https://github.com/avatao-content/baseimage-tutorial-framework
synced 2024-11-22 13:01:32 +00:00
Replace mixins with inotify based observers
This commit is contained in:
parent
597523b3b9
commit
494761d2a7
@ -2,20 +2,29 @@
|
||||
# All Rights Reserved. See LICENSE file for details.
|
||||
|
||||
import logging
|
||||
from os.path import isfile, join, relpath, exists, isdir, realpath
|
||||
from glob import glob
|
||||
from fnmatch import fnmatchcase
|
||||
from typing import Iterable
|
||||
|
||||
from tfw.event_handlers import FrontendEventHandlerBase
|
||||
from tfw.mixins.monitor_manager_mixin import MonitorManagerMixin
|
||||
from tfw.networking import Scope
|
||||
from tfw.event_handlers import FrontendEventHandlerBase, TFWServerUplinkConnector
|
||||
from tfw.components import FileManager
|
||||
from tfw.components.directory_monitor import DirectoryMonitor
|
||||
from tfw.components.inotify import InotifyObserver
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IdeEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
||||
class IdeInotifyObserver(InotifyObserver):
|
||||
def __init__(self, paths):
|
||||
self.uplink = TFWServerUplinkConnector()
|
||||
super().__init__(paths)
|
||||
|
||||
def on_modified(self, event):
|
||||
LOG.debug(event)
|
||||
self.uplink.send_message({
|
||||
'key': 'ide',
|
||||
'data': {'command': 'reload'}
|
||||
}, Scope.WEBSOCKET)
|
||||
|
||||
|
||||
class IdeEventHandler(FrontendEventHandlerBase):
|
||||
# pylint: disable=too-many-arguments,anomalous-backslash-in-string
|
||||
"""
|
||||
Event handler implementing the backend of our browser based IDE.
|
||||
@ -51,12 +60,8 @@ class IdeEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
||||
f'No file(s) in IdeEventHandler working_directory "{directory}"!'
|
||||
)
|
||||
|
||||
MonitorManagerMixin.__init__(
|
||||
self,
|
||||
DirectoryMonitor,
|
||||
self.key,
|
||||
self.filemanager.allowed_directories
|
||||
)
|
||||
self.monitor = IdeInotifyObserver(self.filemanager.allowed_directories)
|
||||
self.monitor.start()
|
||||
|
||||
self.commands = {
|
||||
'read': self.read,
|
||||
@ -91,7 +96,6 @@ class IdeEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
||||
(new file content)
|
||||
|
||||
"""
|
||||
self.monitor.ignore = self.monitor.ignore + 1
|
||||
try:
|
||||
self.filemanager.file_contents = data['content']
|
||||
except Exception: # pylint: disable=broad-except
|
||||
@ -123,7 +127,6 @@ class IdeEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
||||
"""
|
||||
try:
|
||||
self.filemanager.workdir = data['directory']
|
||||
self.reload_monitor()
|
||||
try:
|
||||
self.filemanager.filename = self.filemanager.files[0]
|
||||
self.read(data)
|
||||
|
@ -3,14 +3,57 @@
|
||||
|
||||
import logging
|
||||
|
||||
from tfw.event_handlers import FrontendEventHandlerBase
|
||||
from tfw.mixins.monitor_manager_mixin import MonitorManagerMixin
|
||||
from tfw.components.log_monitor import LogMonitor
|
||||
from os.path import dirname
|
||||
from tfw.networking import Scope
|
||||
from tfw.event_handlers import FrontendEventHandlerBase, TFWServerUplinkConnector
|
||||
from tfw.components.inotify import InotifyObserver
|
||||
from tfw.mixins.supervisor_mixin import SupervisorLogMixin
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LogMonitoringEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
||||
class LogInotifyObserver(InotifyObserver, SupervisorLogMixin):
|
||||
def __init__(self, process_name, log_tail=0):
|
||||
self.prevent_log_recursion()
|
||||
self.uplink = TFWServerUplinkConnector()
|
||||
self.process_name = process_name
|
||||
self.log_tail = log_tail
|
||||
self.procinfo = self.supervisor.getProcessInfo(self.process_name)
|
||||
super().__init__(
|
||||
[dirname(self.procinfo['stdout_logfile']), dirname(self.procinfo['stderr_logfile'])],
|
||||
[self.procinfo['stdout_logfile'], self.procinfo['stderr_logfile']]
|
||||
)
|
||||
|
||||
def reset(self, process_name, log_tail):
|
||||
self.process_name = process_name
|
||||
self.log_tail = log_tail
|
||||
self.procinfo = self.supervisor.getProcessInfo(self.process_name)
|
||||
self.paths = [
|
||||
dirname(self.procinfo['stdout_logfile']),
|
||||
dirname(self.procinfo['stderr_logfile'])
|
||||
]
|
||||
self.patterns = [
|
||||
self.procinfo['stdout_logfile'],
|
||||
self.procinfo['stderr_logfile']
|
||||
]
|
||||
|
||||
def on_modified(self, event):
|
||||
self.uplink.send_message({
|
||||
'key': 'processlog',
|
||||
'data': {
|
||||
'command': 'new_log',
|
||||
'stdout': self.read_stdout(self.process_name, tail=self.log_tail),
|
||||
'stderr': self.read_stderr(self.process_name, tail=self.log_tail)
|
||||
}
|
||||
}, Scope.BROADCAST)
|
||||
|
||||
@staticmethod
|
||||
def prevent_log_recursion():
|
||||
# This is done to prevent inotify event logs triggering themselves (infinite log recursion)
|
||||
logging.getLogger('watchdog.observers.inotify_buffer').propagate = False
|
||||
|
||||
|
||||
class LogMonitoringEventHandler(FrontendEventHandlerBase):
|
||||
"""
|
||||
Monitors the output of a supervisor process (stdout, stderr) and
|
||||
sends the results to the frontend.
|
||||
@ -24,12 +67,8 @@ class LogMonitoringEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
||||
super().__init__(key)
|
||||
self.process_name = process_name
|
||||
self.log_tail = log_tail
|
||||
MonitorManagerMixin.__init__(
|
||||
self,
|
||||
LogMonitor,
|
||||
self.process_name,
|
||||
self.log_tail
|
||||
)
|
||||
self.monitor = LogInotifyObserver(process_name, log_tail)
|
||||
self.monitor.start()
|
||||
|
||||
self.command_handlers = {
|
||||
'process_name': self.handle_process_name,
|
||||
@ -40,7 +79,6 @@ class LogMonitoringEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
||||
try:
|
||||
data = message['data']
|
||||
self.command_handlers[data['command']](data)
|
||||
self.reload_monitor()
|
||||
except KeyError:
|
||||
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)
|
||||
|
||||
@ -51,7 +89,7 @@ class LogMonitoringEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
||||
:param data: TFW message data containing 'value'
|
||||
(name of the process to monitor)
|
||||
"""
|
||||
self.set_monitor_args(data['value'], self.log_tail)
|
||||
self.monitor.reset(data['value'], self.log_tail)
|
||||
|
||||
def handle_log_tail(self, data):
|
||||
"""
|
||||
@ -62,7 +100,7 @@ class LogMonitoringEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
||||
:param data: TFW message data containing 'value'
|
||||
(new tail length)
|
||||
"""
|
||||
self.set_monitor_args(self.process_name, data['value'])
|
||||
self.monitor.reset(self.process_name, data['value'])
|
||||
|
||||
def cleanup(self):
|
||||
self.monitor.stop()
|
||||
|
@ -6,7 +6,6 @@ from xmlrpc.client import Fault as SupervisorFault
|
||||
|
||||
from tfw.event_handlers import FrontendEventHandlerBase
|
||||
from tfw.mixins.supervisor_mixin import SupervisorMixin, SupervisorLogMixin
|
||||
from tfw.components.directory_monitor import with_monitor_paused
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -36,13 +35,11 @@ class ProcessManagingEventHandler(FrontendEventHandlerBase):
|
||||
Commands available: start, stop, restart, readlog
|
||||
(the names are as self-documenting as it gets)
|
||||
"""
|
||||
def __init__(self, key, dirmonitor=None, log_tail=0):
|
||||
def __init__(self, key, log_tail=0):
|
||||
super().__init__(key)
|
||||
self.monitor = dirmonitor
|
||||
self.processmanager = ProcessManager()
|
||||
self.log_tail = log_tail
|
||||
|
||||
@with_monitor_paused
|
||||
def handle_event(self, message):
|
||||
try:
|
||||
data = message['data']
|
||||
|
@ -1,81 +0,0 @@
|
||||
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
|
||||
# All Rights Reserved. See LICENSE file for details.
|
||||
|
||||
import logging
|
||||
from functools import wraps
|
||||
|
||||
from watchdog.events import FileSystemEventHandler as FileSystemWatchdogEventHandler
|
||||
|
||||
from tfw.networking import Scope
|
||||
from tfw.event_handlers import TFWServerUplinkConnector
|
||||
from tfw.decorators.rate_limiter import RateLimiter
|
||||
from tfw.mixins.observer_mixin import ObserverMixin
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DirectoryMonitor(ObserverMixin):
|
||||
def __init__(self, ide_key, directories):
|
||||
self.eventhandler = IdeReloadWatchdogEventHandler(ide_key)
|
||||
for directory in directories:
|
||||
self.observer.schedule(self.eventhandler, directory, recursive=True)
|
||||
|
||||
self.pause, self.resume = self.eventhandler.pause, self.eventhandler.resume
|
||||
|
||||
@property
|
||||
def ignore(self):
|
||||
return self.eventhandler.ignore
|
||||
|
||||
@ignore.setter
|
||||
def ignore(self, value):
|
||||
self.eventhandler.ignore = value if value >= 0 else 0
|
||||
|
||||
@property
|
||||
def pauser(self):
|
||||
return DirectoryMonitor.Pauser(self)
|
||||
|
||||
class Pauser:
|
||||
def __init__(self, directory_monitor):
|
||||
self.directorymonitor = directory_monitor
|
||||
def __enter__(self):
|
||||
self.directorymonitor.pause()
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.directorymonitor.resume()
|
||||
|
||||
|
||||
class IdeReloadWatchdogEventHandler(FileSystemWatchdogEventHandler):
|
||||
def __init__(self, ide_key):
|
||||
super().__init__()
|
||||
self.ide_key = ide_key
|
||||
self.uplink = TFWServerUplinkConnector()
|
||||
self._paused = False
|
||||
self.ignore = 0
|
||||
|
||||
def pause(self):
|
||||
self._paused = True
|
||||
|
||||
def resume(self):
|
||||
self._paused = False
|
||||
|
||||
@RateLimiter(rate_per_second=2)
|
||||
def on_modified(self, event):
|
||||
if self._paused:
|
||||
return
|
||||
if self.ignore > 0:
|
||||
self.ignore = self.ignore - 1
|
||||
return
|
||||
LOG.debug(event)
|
||||
self.uplink.send_message({
|
||||
'key': self.ide_key,
|
||||
'data': {'command': 'reload'}
|
||||
}, Scope.WEBSOCKET)
|
||||
|
||||
|
||||
def with_monitor_paused(fun):
|
||||
@wraps(fun)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if self.monitor:
|
||||
with self.monitor.pauser:
|
||||
return fun(self, *args, **kwargs)
|
||||
return fun(self, *args, **kwargs)
|
||||
return wrapper
|
@ -1,58 +0,0 @@
|
||||
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
|
||||
# All Rights Reserved. See LICENSE file for details.
|
||||
|
||||
import logging
|
||||
from os.path import dirname
|
||||
|
||||
from watchdog.events import PatternMatchingEventHandler as PatternMatchingWatchdogEventHandler
|
||||
|
||||
from tfw.networking import Scope
|
||||
from tfw.event_handlers import TFWServerUplinkConnector
|
||||
from tfw.decorators.rate_limiter import RateLimiter
|
||||
from tfw.mixins.observer_mixin import ObserverMixin
|
||||
from tfw.mixins.supervisor_mixin import SupervisorLogMixin
|
||||
|
||||
|
||||
class LogMonitor(ObserverMixin):
|
||||
def __init__(self, process_name, log_tail=0):
|
||||
self.prevent_log_recursion()
|
||||
event_handler = SendLogWatchdogEventHandler(
|
||||
process_name,
|
||||
log_tail=log_tail
|
||||
)
|
||||
self.observer.schedule(
|
||||
event_handler,
|
||||
event_handler.path
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def prevent_log_recursion():
|
||||
# This is done to prevent inotify event logs triggering themselves (infinite log recursion)
|
||||
logging.getLogger('watchdog.observers.inotify_buffer').propagate = False
|
||||
|
||||
|
||||
class SendLogWatchdogEventHandler(PatternMatchingWatchdogEventHandler, SupervisorLogMixin):
|
||||
def __init__(self, process_name, log_tail=0):
|
||||
self.process_name = process_name
|
||||
self.procinfo = self.supervisor.getProcessInfo(self.process_name)
|
||||
super().__init__([
|
||||
self.procinfo['stdout_logfile'],
|
||||
self.procinfo['stderr_logfile']
|
||||
])
|
||||
self.uplink = TFWServerUplinkConnector()
|
||||
self.log_tail = log_tail
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return dirname(self.procinfo['stdout_logfile'])
|
||||
|
||||
@RateLimiter(rate_per_second=5)
|
||||
def on_modified(self, event):
|
||||
self.uplink.send_message({
|
||||
'key': 'processlog',
|
||||
'data': {
|
||||
'command': 'new_log',
|
||||
'stdout': self.read_stdout(self.process_name, tail=self.log_tail),
|
||||
'stderr': self.read_stderr(self.process_name, tail=self.log_tail)
|
||||
}
|
||||
}, Scope.BROADCAST)
|
@ -1,30 +0,0 @@
|
||||
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
|
||||
# All Rights Reserved. See LICENSE file for details.
|
||||
|
||||
import logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MonitorManagerMixin:
|
||||
def __init__(self, monitor_type, *monitor_args):
|
||||
self._monitor_type = monitor_type
|
||||
self._monitor = None
|
||||
self.monitor_args = monitor_args
|
||||
self.reload_monitor()
|
||||
|
||||
@property
|
||||
def monitor(self):
|
||||
return self._monitor
|
||||
|
||||
def set_monitor_args(self, *monitor_args):
|
||||
self.monitor_args = monitor_args
|
||||
|
||||
def reload_monitor(self):
|
||||
if self._monitor:
|
||||
try:
|
||||
self._monitor.stop()
|
||||
except KeyError:
|
||||
LOG.debug('Working directory was removed – ignoring...')
|
||||
self._monitor = self._monitor_type(*self.monitor_args)
|
||||
self._monitor.watch() # This runs on a separate thread
|
@ -1,20 +0,0 @@
|
||||
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
|
||||
# All Rights Reserved. See LICENSE file for details.
|
||||
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from tfw.decorators.lazy_property import lazy_property
|
||||
|
||||
|
||||
class ObserverMixin:
|
||||
@lazy_property
|
||||
def observer(self):
|
||||
# pylint: disable=no-self-use
|
||||
return Observer()
|
||||
|
||||
def watch(self):
|
||||
self.observer.start()
|
||||
|
||||
def stop(self):
|
||||
self.observer.stop()
|
||||
self.observer.join()
|
Loading…
Reference in New Issue
Block a user