mirror of
https://github.com/avatao-content/baseimage-tutorial-framework
synced 2024-11-22 18:51:31 +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.
|
# All Rights Reserved. See LICENSE file for details.
|
||||||
|
|
||||||
import logging
|
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.networking import Scope
|
||||||
from tfw.mixins.monitor_manager_mixin import MonitorManagerMixin
|
from tfw.event_handlers import FrontendEventHandlerBase, TFWServerUplinkConnector
|
||||||
from tfw.components import FileManager
|
from tfw.components import FileManager
|
||||||
from tfw.components.directory_monitor import DirectoryMonitor
|
from tfw.components.inotify import InotifyObserver
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
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
|
# pylint: disable=too-many-arguments,anomalous-backslash-in-string
|
||||||
"""
|
"""
|
||||||
Event handler implementing the backend of our browser based IDE.
|
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}"!'
|
f'No file(s) in IdeEventHandler working_directory "{directory}"!'
|
||||||
)
|
)
|
||||||
|
|
||||||
MonitorManagerMixin.__init__(
|
self.monitor = IdeInotifyObserver(self.filemanager.allowed_directories)
|
||||||
self,
|
self.monitor.start()
|
||||||
DirectoryMonitor,
|
|
||||||
self.key,
|
|
||||||
self.filemanager.allowed_directories
|
|
||||||
)
|
|
||||||
|
|
||||||
self.commands = {
|
self.commands = {
|
||||||
'read': self.read,
|
'read': self.read,
|
||||||
@ -91,7 +96,6 @@ class IdeEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
|||||||
(new file content)
|
(new file content)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.monitor.ignore = self.monitor.ignore + 1
|
|
||||||
try:
|
try:
|
||||||
self.filemanager.file_contents = data['content']
|
self.filemanager.file_contents = data['content']
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
@ -123,7 +127,6 @@ class IdeEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.filemanager.workdir = data['directory']
|
self.filemanager.workdir = data['directory']
|
||||||
self.reload_monitor()
|
|
||||||
try:
|
try:
|
||||||
self.filemanager.filename = self.filemanager.files[0]
|
self.filemanager.filename = self.filemanager.files[0]
|
||||||
self.read(data)
|
self.read(data)
|
||||||
|
@ -3,14 +3,57 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from tfw.event_handlers import FrontendEventHandlerBase
|
from os.path import dirname
|
||||||
from tfw.mixins.monitor_manager_mixin import MonitorManagerMixin
|
from tfw.networking import Scope
|
||||||
from tfw.components.log_monitor import LogMonitor
|
from tfw.event_handlers import FrontendEventHandlerBase, TFWServerUplinkConnector
|
||||||
|
from tfw.components.inotify import InotifyObserver
|
||||||
|
from tfw.mixins.supervisor_mixin import SupervisorLogMixin
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
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
|
Monitors the output of a supervisor process (stdout, stderr) and
|
||||||
sends the results to the frontend.
|
sends the results to the frontend.
|
||||||
@ -24,12 +67,8 @@ class LogMonitoringEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
|||||||
super().__init__(key)
|
super().__init__(key)
|
||||||
self.process_name = process_name
|
self.process_name = process_name
|
||||||
self.log_tail = log_tail
|
self.log_tail = log_tail
|
||||||
MonitorManagerMixin.__init__(
|
self.monitor = LogInotifyObserver(process_name, log_tail)
|
||||||
self,
|
self.monitor.start()
|
||||||
LogMonitor,
|
|
||||||
self.process_name,
|
|
||||||
self.log_tail
|
|
||||||
)
|
|
||||||
|
|
||||||
self.command_handlers = {
|
self.command_handlers = {
|
||||||
'process_name': self.handle_process_name,
|
'process_name': self.handle_process_name,
|
||||||
@ -40,7 +79,6 @@ class LogMonitoringEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
|||||||
try:
|
try:
|
||||||
data = message['data']
|
data = message['data']
|
||||||
self.command_handlers[data['command']](data)
|
self.command_handlers[data['command']](data)
|
||||||
self.reload_monitor()
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)
|
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)
|
||||||
|
|
||||||
@ -51,7 +89,7 @@ class LogMonitoringEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
|||||||
:param data: TFW message data containing 'value'
|
:param data: TFW message data containing 'value'
|
||||||
(name of the process to monitor)
|
(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):
|
def handle_log_tail(self, data):
|
||||||
"""
|
"""
|
||||||
@ -62,7 +100,7 @@ class LogMonitoringEventHandler(FrontendEventHandlerBase, MonitorManagerMixin):
|
|||||||
:param data: TFW message data containing 'value'
|
:param data: TFW message data containing 'value'
|
||||||
(new tail length)
|
(new tail length)
|
||||||
"""
|
"""
|
||||||
self.set_monitor_args(self.process_name, data['value'])
|
self.monitor.reset(self.process_name, data['value'])
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
self.monitor.stop()
|
self.monitor.stop()
|
||||||
|
@ -6,7 +6,6 @@ from xmlrpc.client import Fault as SupervisorFault
|
|||||||
|
|
||||||
from tfw.event_handlers import FrontendEventHandlerBase
|
from tfw.event_handlers import FrontendEventHandlerBase
|
||||||
from tfw.mixins.supervisor_mixin import SupervisorMixin, SupervisorLogMixin
|
from tfw.mixins.supervisor_mixin import SupervisorMixin, SupervisorLogMixin
|
||||||
from tfw.components.directory_monitor import with_monitor_paused
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -36,13 +35,11 @@ class ProcessManagingEventHandler(FrontendEventHandlerBase):
|
|||||||
Commands available: start, stop, restart, readlog
|
Commands available: start, stop, restart, readlog
|
||||||
(the names are as self-documenting as it gets)
|
(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)
|
super().__init__(key)
|
||||||
self.monitor = dirmonitor
|
|
||||||
self.processmanager = ProcessManager()
|
self.processmanager = ProcessManager()
|
||||||
self.log_tail = log_tail
|
self.log_tail = log_tail
|
||||||
|
|
||||||
@with_monitor_paused
|
|
||||||
def handle_event(self, message):
|
def handle_event(self, message):
|
||||||
try:
|
try:
|
||||||
data = message['data']
|
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