Replace mixins with inotify based observers

This commit is contained in:
R. Richard 2019-06-24 14:29:31 +02:00 committed by Kristóf Tóth
parent 597523b3b9
commit 494761d2a7
7 changed files with 71 additions and 222 deletions

View File

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

View File

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

View File

@ -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']

View File

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

View File

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

View File

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

View File

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