Simplify IDE handler and file manager

This commit is contained in:
R. Richard
2019-08-07 09:44:03 +02:00
parent e6d2777520
commit d31a850a4e
3 changed files with 127 additions and 299 deletions

View File

@ -1,4 +1,5 @@
import logging
from os.path import isfile
from tfw.internals.networking import Scope
from tfw.internals.inotify import InotifyObserver
@ -32,161 +33,62 @@ BUILD_ARTIFACTS = (
class IdeHandler:
keys = ['ide']
# pylint: disable=too-many-arguments,anomalous-backslash-in-string
"""
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.
keys = ['ide.read', 'ide.write']
When any file in the selected directory changes they are automatically refreshed
on the frontend (this is done by listening to inotify events).
This EventHandler accepts messages that have a data['command'] key specifying
a command to be executed.
The API of each command is documented in their respective handler.
"""
def __init__(self, *, directory, allowed_directories, selected_file=None, exclude=None):
"""
: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
:param selected_file: file that is selected by default
:param exclude: list of filenames that should not appear between files (for .o, .pyc, etc.)
"""
def __init__(self, *, patterns, initial_file=''):
self.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}"!'
)
self.filemanager = FileManager(patterns)
self._initial_file = initial_file
self.monitor = InotifyObserver(
self.filemanager.allowed_directories,
path=self.filemanager.parents,
exclude=BUILD_ARTIFACTS
)
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
'ide.read' : self.read,
'ide.write' : self.write
}
@property
def initial_file(self):
if not isfile(self._initial_file):
self._initial_file = self.filemanager.files[0]
return self._initial_file
def _reload_frontend(self, event): # pylint: disable=unused-argument
self.send_message({
'key': 'ide',
'data': {'command': 'reload'}
})
self.send_message({'key': 'ide.reload'})
def send_message(self, message):
self.connector.send_message(message, scope=Scope.WEBSOCKET)
def read(self, data):
"""
Read the currently selected file.
:return dict: TFW message data containing key 'content'
(contents of the selected file)
"""
def read(self, message):
if message.get('files'):
self.filemanager.patterns = message['files']
try:
data['content'] = self.filemanager.file_contents
message['content'] = self.filemanager.read_file(message['filename'])
except PermissionError:
data['content'] = 'You have no permission to open that file :('
message['content'] = 'You have no permission to open that file :('
except FileNotFoundError:
data['content'] = 'This file was removed :('
message['content'] = 'This file was removed :('
except Exception: # pylint: disable=broad-except
data['content'] = 'Failed to read file :('
return data
message['content'] = 'Failed to read file :('
def write(self, data):
"""
Overwrites a file with the desired string.
:param data: TFW message data containing key 'content'
(new file content)
"""
def write(self, message):
try:
self.filemanager.file_contents = data['content']
self.filemanager.write_file(message['filename'], message['content'])
except Exception: # pylint: disable=broad-except
LOG.exception('Error writing file!')
del data['content']
return data
def select(self, data):
"""
Selects a file from the current directory.
:param data: TFW message data containing 'filename'
(name of file to select relative to the current directory)
"""
try:
self.filemanager.filename = data['filename']
except EnvironmentError:
LOG.exception('Failed to select file "%s"', data['filename'])
return data
def select_dir(self, data):
"""
Select a new working directory to display files from.
:param data: TFW message data containing 'directory'
(absolute path of diretory to select.
must be a path whitelisted in
self.allowed_directories)
"""
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 :('
except EnvironmentError as err:
LOG.error(
'Failed to select directory "%s". Reason: %s',
data['directory'], str(err)
)
return data
def exclude(self, data):
"""
Overwrite list of excluded files
:param data: TFW message data containing 'exclude'
(list of unix-style filename patterns to be excluded,
e.g.: ["\*.pyc", "\*.o")
"""
try:
self.filemanager.exclude = list(data['exclude'])
except TypeError:
LOG.error('Exclude must be Iterable!')
return data
def attach_fileinfo(self, data):
"""
Basic information included in every response to the frontend.
"""
data['filename'] = self.filemanager.filename
data['files'] = self.filemanager.files
data['directory'] = self.filemanager.workdir
del message['content']
def handle_event(self, message, _):
try:
data = message['data']
message['data'] = self.commands[data['command']](data)
self.attach_fileinfo(data)
if message['filename'] == '':
message['filename'] = self.initial_file
self.commands[message['key']](message)
message['files'] = self.filemanager.files
self.send_message(message)
except KeyError:
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)