mirror of
https://github.com/avatao-content/baseimage-tutorial-framework
synced 2024-11-22 18:21:31 +00:00
Make files observable and refactor event handlers
This commit is contained in:
parent
778f155580
commit
ffe512776a
@ -10,18 +10,28 @@ from tfw.components.inotify import InotifyObserver
|
|||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
BUILD_ARTIFACTS = [
|
||||||
class IdeInotifyObserver(InotifyObserver):
|
"*.a",
|
||||||
def __init__(self, paths):
|
"*.class",
|
||||||
self.uplink = TFWServerUplinkConnector()
|
"*.dll",
|
||||||
super().__init__(paths)
|
"*.dylib",
|
||||||
|
"*.elf",
|
||||||
def on_modified(self, event):
|
"*.exe",
|
||||||
LOG.debug(event)
|
"*.jar",
|
||||||
self.uplink.send_message({
|
"*.ko",
|
||||||
'key': 'ide',
|
"*.la",
|
||||||
'data': {'command': 'reload'}
|
"*.lib",
|
||||||
}, Scope.WEBSOCKET)
|
"*.lo",
|
||||||
|
"*.o",
|
||||||
|
"*.obj",
|
||||||
|
"*.out",
|
||||||
|
"*.py[cod]",
|
||||||
|
"*.so",
|
||||||
|
"*.so.*",
|
||||||
|
"*.tar.gz",
|
||||||
|
"*.zip",
|
||||||
|
"*__pycache__*"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class IdeEventHandler(FrontendEventHandlerBase):
|
class IdeEventHandler(FrontendEventHandlerBase):
|
||||||
@ -60,7 +70,14 @@ class IdeEventHandler(FrontendEventHandlerBase):
|
|||||||
f'No file(s) in IdeEventHandler working_directory "{directory}"!'
|
f'No file(s) in IdeEventHandler working_directory "{directory}"!'
|
||||||
)
|
)
|
||||||
|
|
||||||
self.monitor = IdeInotifyObserver(self.filemanager.allowed_directories)
|
self.monitor = InotifyObserver(self.filemanager.allowed_directories, exclude=BUILD_ARTIFACTS)
|
||||||
|
def on_modified(event):
|
||||||
|
LOG.debug(event)
|
||||||
|
self.server_connector.send_message({
|
||||||
|
'key': 'ide',
|
||||||
|
'data': {'command': 'reload'}
|
||||||
|
}, Scope.WEBSOCKET)
|
||||||
|
self.monitor.on_modified = on_modified
|
||||||
self.monitor.start()
|
self.monitor.start()
|
||||||
|
|
||||||
self.commands = {
|
self.commands = {
|
||||||
|
@ -13,32 +13,26 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class LogInotifyObserver(InotifyObserver, SupervisorLogMixin):
|
class LogInotifyObserver(InotifyObserver, SupervisorLogMixin):
|
||||||
def __init__(self, process_name, log_tail=0):
|
def __init__(self, server_connector, process_name, log_tail=0):
|
||||||
self.prevent_log_recursion()
|
self.prevent_log_recursion()
|
||||||
self.uplink = TFWServerUplinkConnector()
|
self.server_connector = server_connector
|
||||||
self.process_name = process_name
|
self._process_name = process_name
|
||||||
self.log_tail = log_tail
|
self.log_tail = log_tail
|
||||||
self.procinfo = self.supervisor.getProcessInfo(self.process_name)
|
self.procinfo = self.supervisor.getProcessInfo(self.process_name)
|
||||||
super().__init__(
|
InotifyObserver.__init__(self, [self.procinfo['stdout_logfile'], self.procinfo['stderr_logfile']])
|
||||||
[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):
|
@property
|
||||||
self.process_name = process_name
|
def process_name(self):
|
||||||
self.log_tail = log_tail
|
return self._process_name
|
||||||
self.procinfo = self.supervisor.getProcessInfo(self.process_name)
|
|
||||||
self.paths = [
|
@process_name.setter
|
||||||
dirname(self.procinfo['stdout_logfile']),
|
def process_name(self, process_name):
|
||||||
dirname(self.procinfo['stderr_logfile'])
|
self._process_name = process_name
|
||||||
]
|
self.procinfo = self.supervisor.getProcessInfo(process_name)
|
||||||
self.patterns = [
|
self.paths = [self.procinfo['stdout_logfile'], self.procinfo['stderr_logfile']]
|
||||||
self.procinfo['stdout_logfile'],
|
|
||||||
self.procinfo['stderr_logfile']
|
|
||||||
]
|
|
||||||
|
|
||||||
def on_modified(self, event):
|
def on_modified(self, event):
|
||||||
self.uplink.send_message({
|
self.server_connector.send_message({
|
||||||
'key': 'processlog',
|
'key': 'processlog',
|
||||||
'data': {
|
'data': {
|
||||||
'command': 'new_log',
|
'command': 'new_log',
|
||||||
@ -67,7 +61,7 @@ class LogMonitoringEventHandler(FrontendEventHandlerBase):
|
|||||||
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
|
||||||
self.monitor = LogInotifyObserver(process_name, log_tail)
|
self.monitor = LogInotifyObserver(self.server_connector, process_name, log_tail)
|
||||||
self.monitor.start()
|
self.monitor.start()
|
||||||
|
|
||||||
self.command_handlers = {
|
self.command_handlers = {
|
||||||
@ -89,7 +83,7 @@ class LogMonitoringEventHandler(FrontendEventHandlerBase):
|
|||||||
: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.monitor.reset(data['value'], self.log_tail)
|
self.monitor.process_name = data['value']
|
||||||
|
|
||||||
def handle_log_tail(self, data):
|
def handle_log_tail(self, data):
|
||||||
"""
|
"""
|
||||||
@ -100,7 +94,7 @@ class LogMonitoringEventHandler(FrontendEventHandlerBase):
|
|||||||
:param data: TFW message data containing 'value'
|
:param data: TFW message data containing 'value'
|
||||||
(new tail length)
|
(new tail length)
|
||||||
"""
|
"""
|
||||||
self.monitor.reset(self.process_name, data['value'])
|
self.monitor.log_tail = data['value']
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
self.monitor.stop()
|
self.monitor.stop()
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from tfw.event_handlers import FrontendEventHandlerBase
|
from tfw.event_handlers import FrontendEventHandlerBase
|
||||||
|
from tfw.components import BashMonitor
|
||||||
from tfw.components.terminado_mini_server import TerminadoMiniServer
|
from tfw.components.terminado_mini_server import TerminadoMiniServer
|
||||||
from tfw.config import TFWENV
|
from tfw.config import TFWENV
|
||||||
from tao.config import TAOENV
|
from tao.config import TAOENV
|
||||||
@ -21,13 +22,13 @@ class TerminalEventHandler(FrontendEventHandlerBase):
|
|||||||
a command to be executed.
|
a command to be executed.
|
||||||
The API of each command is documented in their respective handler.
|
The API of each command is documented in their respective handler.
|
||||||
"""
|
"""
|
||||||
def __init__(self, key, monitor):
|
def __init__(self, key):
|
||||||
"""
|
"""
|
||||||
:param key: key this EventHandler listens to
|
:param key: key this EventHandler listens to
|
||||||
:param monitor: tfw.components.HistoryMonitor instance to read command history from
|
:param monitor: tfw.components.HistoryMonitor instance to read command history from
|
||||||
"""
|
"""
|
||||||
super().__init__(key)
|
super().__init__(key)
|
||||||
self._historymonitor = monitor
|
self._historymonitor = BashMonitor(self.server_connector, TFWENV.HISTFILE)
|
||||||
bash_as_user_cmd = ['sudo', '-u', TAOENV.USER, 'bash']
|
bash_as_user_cmd = ['sudo', '-u', TAOENV.USER, 'bash']
|
||||||
|
|
||||||
self.terminado_server = TerminadoMiniServer(
|
self.terminado_server = TerminadoMiniServer(
|
||||||
@ -42,7 +43,6 @@ class TerminalEventHandler(FrontendEventHandlerBase):
|
|||||||
'read': self.read
|
'read': self.read
|
||||||
}
|
}
|
||||||
|
|
||||||
if self._historymonitor:
|
|
||||||
self._historymonitor.start()
|
self._historymonitor.start()
|
||||||
self.terminado_server.listen()
|
self.terminado_server.listen()
|
||||||
|
|
||||||
@ -84,5 +84,4 @@ class TerminalEventHandler(FrontendEventHandlerBase):
|
|||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
self.terminado_server.stop()
|
self.terminado_server.stop()
|
||||||
if self.historymonitor:
|
|
||||||
self.historymonitor.stop()
|
self.historymonitor.stop()
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
|
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
|
||||||
# All Rights Reserved. See LICENSE file for details.
|
# All Rights Reserved. See LICENSE file for details.
|
||||||
|
|
||||||
|
from pwd import getpwnam
|
||||||
|
from grp import getgrnam
|
||||||
|
from pathlib import Path
|
||||||
|
from os import chown
|
||||||
from os.path import dirname
|
from os.path import dirname
|
||||||
from re import findall
|
from re import findall
|
||||||
from re import compile as compileregex
|
from re import compile as compileregex
|
||||||
@ -22,30 +26,39 @@ class HistoryMonitor(ABC, InotifyObserver):
|
|||||||
command pattern property and optionally the sanitize_command method.
|
command pattern property and optionally the sanitize_command method.
|
||||||
See examples below.
|
See examples below.
|
||||||
"""
|
"""
|
||||||
def __init__(self, domain, histfile):
|
def __init__(self, uplink, histfile):
|
||||||
self.domain = domain
|
self._domain = ''
|
||||||
self.histfile = histfile
|
self.histfile = histfile
|
||||||
self._history = []
|
self.history = []
|
||||||
self._last_length = len(self._history)
|
self._last_length = len(self.history)
|
||||||
self.uplink = TFWServerUplinkConnector()
|
self.uplink = uplink
|
||||||
super().__init__(dirname(self.histfile), [self.histfile])
|
uid = getpwnam('user').pw_uid
|
||||||
|
gid = getgrnam('users').gr_gid
|
||||||
|
path = Path(self.histfile)
|
||||||
|
path.touch()
|
||||||
|
chown(self.histfile, uid, gid)
|
||||||
|
super().__init__(self.histfile)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def domain(self):
|
||||||
|
return self._domain
|
||||||
|
|
||||||
|
@domain.setter
|
||||||
|
def domain(self, domain):
|
||||||
|
self._domain = domain
|
||||||
|
|
||||||
def on_modified(self, event):
|
def on_modified(self, event):
|
||||||
self._fetch_history()
|
self._fetch_history()
|
||||||
if self._last_length < len(self._history):
|
if self._last_length < len(self.history):
|
||||||
for command in self._history[self._last_length:]:
|
for command in self.history[self._last_length:]:
|
||||||
self.send_message(command)
|
self.send_message(command)
|
||||||
|
|
||||||
@property
|
|
||||||
def history(self):
|
|
||||||
return self._history
|
|
||||||
|
|
||||||
def _fetch_history(self):
|
def _fetch_history(self):
|
||||||
self._last_length = len(self._history)
|
self._last_length = len(self.history)
|
||||||
with open(self.histfile, 'r') as ifile:
|
with open(self.histfile, 'r') as ifile:
|
||||||
pattern = compileregex(self.command_pattern)
|
pattern = compileregex(self.command_pattern)
|
||||||
data = ifile.read()
|
data = ifile.read()
|
||||||
self._history = [
|
self.history = [
|
||||||
self.sanitize_command(command)
|
self.sanitize_command(command)
|
||||||
for command in findall(pattern, data)
|
for command in findall(pattern, data)
|
||||||
]
|
]
|
||||||
@ -76,8 +89,9 @@ class BashMonitor(HistoryMonitor):
|
|||||||
shopt -s histappend
|
shopt -s histappend
|
||||||
unset HISTCONTROL
|
unset HISTCONTROL
|
||||||
"""
|
"""
|
||||||
def __init__(self, histfile):
|
def __init__(self, uplink, histfile):
|
||||||
super().__init__('bash', histfile)
|
super().__init__(uplink, histfile)
|
||||||
|
self.domain = 'bash'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def command_pattern(self):
|
def command_pattern(self):
|
||||||
@ -93,7 +107,8 @@ class GDBMonitor(HistoryMonitor):
|
|||||||
For this to work "set trace-commands on" must be set in GDB.
|
For this to work "set trace-commands on" must be set in GDB.
|
||||||
"""
|
"""
|
||||||
def __init__(self, histfile):
|
def __init__(self, histfile):
|
||||||
super().__init__('gdb', histfile)
|
super().__init__(histfile)
|
||||||
|
self.domain = 'gdb'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def command_pattern(self):
|
def command_pattern(self):
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
|
|
||||||
from time import time
|
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
from time import time
|
||||||
|
from os.path import abspath, dirname, isfile
|
||||||
|
|
||||||
from watchdog.observers import Observer
|
from watchdog.observers import Observer
|
||||||
from watchdog.events import FileSystemMovedEvent, PatternMatchingEventHandler
|
from watchdog.events import FileSystemMovedEvent, PatternMatchingEventHandler
|
||||||
@ -65,28 +66,44 @@ class InotifyDirDeletedEvent(InotifyEvent):
|
|||||||
|
|
||||||
|
|
||||||
class InotifyObserver:
|
class InotifyObserver:
|
||||||
def __init__(self, paths, patterns=None, exclude=None, recursive=False):
|
def __init__(self, path, patterns=[], exclude=None, recursive=False):
|
||||||
self._paths = paths
|
self._files = []
|
||||||
|
self._paths = path
|
||||||
self._patterns = patterns
|
self._patterns = patterns
|
||||||
self._exclude = exclude
|
self._exclude = exclude
|
||||||
self._recursive = recursive
|
self._recursive = recursive
|
||||||
self._observer = Observer()
|
self._observer = Observer()
|
||||||
self._reset(paths, patterns, exclude)
|
self._reset()
|
||||||
|
|
||||||
def _reset(self, paths, patterns, exclude):
|
def _reset(self):
|
||||||
_dispatch_event = self._dispatch_event
|
dispatch_event = self._dispatch_event
|
||||||
class TransformerEventHandler(PatternMatchingEventHandler):
|
class TransformerEventHandler(PatternMatchingEventHandler):
|
||||||
def on_any_event(self, event):
|
def on_any_event(self, event):
|
||||||
_dispatch_event(event)
|
dispatch_event(event)
|
||||||
self.handler = TransformerEventHandler(patterns, exclude)
|
|
||||||
self._observer.unschedule_all()
|
if isinstance(self._paths, str):
|
||||||
if isinstance(paths, str):
|
self._paths = [self._paths]
|
||||||
self._observer.schedule(self.handler, paths, self._recursive)
|
if isinstance(self._paths, Iterable):
|
||||||
elif isinstance(paths, Iterable):
|
self._extract_files_from_paths()
|
||||||
for path in paths:
|
|
||||||
self._observer.schedule(self.handler, path, self._recursive)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError('Expected one or more strings representing paths.')
|
raise ValueError('Expected one or more string paths.')
|
||||||
|
|
||||||
|
patterns = self._files+self.patterns
|
||||||
|
self.handler = TransformerEventHandler(patterns if patterns else None, self.exclude)
|
||||||
|
self._observer.unschedule_all()
|
||||||
|
for path in self.paths:
|
||||||
|
self._observer.schedule(self.handler, path, self._recursive)
|
||||||
|
|
||||||
|
def _extract_files_from_paths(self):
|
||||||
|
files, paths = [], []
|
||||||
|
for path in self._paths:
|
||||||
|
if isfile(path):
|
||||||
|
new_file = abspath(path)
|
||||||
|
files.append(new_file)
|
||||||
|
paths.append(dirname(new_file))
|
||||||
|
else:
|
||||||
|
paths.append(path)
|
||||||
|
self._files, self._paths = files, paths
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def paths(self):
|
def paths(self):
|
||||||
@ -95,7 +112,7 @@ class InotifyObserver:
|
|||||||
@paths.setter
|
@paths.setter
|
||||||
def paths(self, paths):
|
def paths(self, paths):
|
||||||
self._paths = paths
|
self._paths = paths
|
||||||
self._reset(paths, self._patterns, self._exclude)
|
self._reset()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def patterns(self):
|
def patterns(self):
|
||||||
@ -104,7 +121,7 @@ class InotifyObserver:
|
|||||||
@patterns.setter
|
@patterns.setter
|
||||||
def patterns(self, patterns):
|
def patterns(self, patterns):
|
||||||
self._patterns = patterns
|
self._patterns = patterns
|
||||||
self._reset(self._paths, patterns, self._exclude)
|
self._reset()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def exclude(self):
|
def exclude(self):
|
||||||
@ -113,7 +130,7 @@ class InotifyObserver:
|
|||||||
@exclude.setter
|
@exclude.setter
|
||||||
def exclude(self, exclude):
|
def exclude(self, exclude):
|
||||||
self._exclude = exclude
|
self._exclude = exclude
|
||||||
self._reset(self._paths, self._patterns, exclude)
|
self._reset()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self._observer.start()
|
self._observer.start()
|
||||||
|
@ -11,8 +11,8 @@ from tempfile import TemporaryDirectory
|
|||||||
import watchdog
|
import watchdog
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from inotify import InotifyObserver
|
from .inotify import InotifyObserver
|
||||||
from inotify import (
|
from .inotify import (
|
||||||
InotifyFileCreatedEvent, InotifyFileModifiedEvent, InotifyFileMovedEvent,
|
InotifyFileCreatedEvent, InotifyFileModifiedEvent, InotifyFileMovedEvent,
|
||||||
InotifyFileDeletedEvent, InotifyDirCreatedEvent, InotifyDirModifiedEvent,
|
InotifyFileDeletedEvent, InotifyDirCreatedEvent, InotifyDirModifiedEvent,
|
||||||
InotifyDirMovedEvent, InotifyDirDeletedEvent
|
InotifyDirMovedEvent, InotifyDirDeletedEvent
|
||||||
@ -113,8 +113,8 @@ def test_create(context):
|
|||||||
assert context.check_any()
|
assert context.check_any()
|
||||||
|
|
||||||
def test_modify(context):
|
def test_modify(context):
|
||||||
with open(context.subfile, 'w') as ofile:
|
with open(context.subfile, 'wb', buffering=0) as ofile:
|
||||||
ofile.write('text')
|
ofile.write(b'text')
|
||||||
context.check_event(InotifyFileModifiedEvent, context.subfile)
|
context.check_event(InotifyFileModifiedEvent, context.subfile)
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
Loading…
Reference in New Issue
Block a user