baseimage-tutorial-framework/lib/tfw/builtins/ide_event_handler.py

196 lines
6.2 KiB
Python
Raw Normal View History

2019-06-10 13:32:45 +00:00
import logging
from tfw.networking import Scope
from tfw.components import FileManager
from tfw.components.inotify import InotifyObserver
2019-06-28 13:11:02 +00:00
2019-05-20 12:52:02 +00:00
LOG = logging.getLogger(__name__)
2019-06-27 12:48:27 +00:00
BUILD_ARTIFACTS = (
"*.a",
"*.class",
"*.dll",
"*.dylib",
"*.elf",
"*.exe",
"*.jar",
"*.ko",
"*.la",
"*.lib",
"*.lo",
"*.o",
"*.obj",
"*.out",
"*.py[cod]",
"*.so",
"*.so.*",
"*.tar.gz",
"*.zip",
"*__pycache__*"
2019-06-27 12:48:27 +00:00
)
class IdeEventHandler:
keys = ['ide']
# pylint: disable=too-many-arguments,anomalous-backslash-in-string
2018-04-18 17:44:26 +00:00
"""
Event handler implementing the backend of our browser based IDE.
By default all files in the directory specified in __init__ are displayed
on the fontend. Note that this is a stateful component.
When any file in the selected directory changes they are automatically refreshed
on the frontend (this is done by listening to inotify events).
2018-06-01 14:20:20 +00:00
This EventHandler accepts messages that have a data['command'] key specifying
2018-04-18 17:44:26 +00:00
a command to be executed.
2018-06-01 14:20:20 +00:00
The API of each command is documented in their respective handler.
2018-04-18 17:44:26 +00:00
"""
def __init__(self, directory, allowed_directories, selected_file=None, exclude=None):
2018-04-18 17:44:26 +00:00
"""
:param key: the key this instance should listen to
:param directory: working directory which the EventHandler should serve files from
:param allowed_directories: list of directories that can be switched to using selectdir
2018-04-18 17:44:26 +00:00
:param selected_file: file that is selected by default
2018-06-01 14:20:20 +00:00
:param exclude: list of filenames that should not appear between files (for .o, .pyc, etc.)
2018-04-18 17:44:26 +00:00
"""
self.server_connector = None
try:
self.filemanager = FileManager(
allowed_directories=allowed_directories,
working_directory=directory,
selected_file=selected_file,
exclude=exclude
)
except IndexError:
raise EnvironmentError(
f'No file(s) in IdeEventHandler working_directory "{directory}"!'
)
2019-06-28 13:11:02 +00:00
self.monitor = InotifyObserver(
self.filemanager.allowed_directories,
exclude=BUILD_ARTIFACTS
)
2019-06-27 12:48:27 +00:00
self.monitor.on_modified = self._reload_frontend
self.monitor.start()
self.commands = {
'read': self.read,
'write': self.write,
'select': self.select,
'selectdir': self.select_dir,
'exclude': self.exclude
}
2019-06-28 13:11:02 +00:00
def _reload_frontend(self, event): # pylint: disable=unused-argument
self.send_message({
2019-06-27 12:48:27 +00:00
'key': 'ide',
'data': {'command': 'reload'}
})
def send_message(self, message):
self.server_connector.send_message(message, scope=Scope.WEBSOCKET)
2019-06-27 12:48:27 +00:00
def read(self, data):
2018-04-18 17:44:26 +00:00
"""
Read the currently selected file.
2018-08-04 21:27:18 +00:00
:return dict: TFW message data containing key 'content'
(contents of the selected file)
2018-04-18 17:44:26 +00:00
"""
try:
data['content'] = self.filemanager.file_contents
except PermissionError:
data['content'] = 'You have no permission to open that file :('
except FileNotFoundError:
data['content'] = 'This file was removed :('
except Exception: # pylint: disable=broad-except
data['content'] = 'Failed to read file :('
return data
def write(self, data):
2018-04-18 17:44:26 +00:00
"""
Overwrites a file with the desired string.
2018-06-01 14:20:20 +00:00
:param data: TFW message data containing key 'content'
(new file content)
2018-04-18 17:44:26 +00:00
"""
try:
self.filemanager.file_contents = data['content']
except Exception: # pylint: disable=broad-except
LOG.exception('Error writing file!')
del data['content']
return data
def select(self, data):
2018-04-18 17:44:26 +00:00
"""
Selects a file from the current directory.
2018-06-01 14:20:20 +00:00
:param data: TFW message data containing 'filename'
(name of file to select relative to the current directory)
2018-04-18 17:44:26 +00:00
"""
try:
self.filemanager.filename = data['filename']
except EnvironmentError:
LOG.exception('Failed to select file "%s"', data['filename'])
return data
def select_dir(self, data):
2018-04-18 17:44:26 +00:00
"""
Select a new working directory to display files from.
2018-06-01 14:20:20 +00:00
:param data: TFW message data containing 'directory'
(absolute path of diretory to select.
must be a path whitelisted in
self.allowed_directories)
2018-04-18 17:44:26 +00:00
"""
try:
self.filemanager.workdir = data['directory']
try:
self.filemanager.filename = self.filemanager.files[0]
self.read(data)
except IndexError:
data['content'] = 'No files in this directory :('
2018-04-06 14:09:05 +00:00
except EnvironmentError as err:
2019-06-28 13:11:02 +00:00
LOG.error(
'Failed to select directory "%s". Reason: %s',
data['directory'], str(err)
)
return data
def exclude(self, data):
2018-04-18 17:44:26 +00:00
"""
Overwrite list of excluded files
2018-06-01 14:20:20 +00:00
:param data: TFW message data containing 'exclude'
(list of unix-style filename patterns to be excluded,
e.g.: ["\*.pyc", "\*.o")
2018-04-18 17:44:26 +00:00
"""
try:
self.filemanager.exclude = list(data['exclude'])
except TypeError:
LOG.error('Exclude must be Iterable!')
return data
def attach_fileinfo(self, data):
2018-04-18 17:44:26 +00:00
"""
Basic information included in every response to the frontend.
"""
data['filename'] = self.filemanager.filename
data['files'] = self.filemanager.files
data['directory'] = self.filemanager.workdir
def handle_event(self, message, _):
try:
data = message['data']
message['data'] = self.commands[data['command']](data)
self.attach_fileinfo(data)
self.send_message(message)
except KeyError:
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)
2018-02-13 14:38:46 +00:00
def cleanup(self):
self.monitor.stop()