diff --git a/lib/tfw/components/__init__.py b/lib/tfw/components/__init__.py index 12735f9..ad160f1 100644 --- a/lib/tfw/components/__init__.py +++ b/lib/tfw/components/__init__.py @@ -8,3 +8,4 @@ from .ide_event_handler import IdeEventHandler from .history_monitor import HistoryMonitor, BashMonitor, GDBMonitor from .terminal_commands import TerminalCommands from .log_monitoring_event_handler import LogMonitoringEventHandler +from .fsm_managing_event_handler import FSMManagingEventHandler diff --git a/lib/tfw/components/fsm_managing_event_handler.py b/lib/tfw/components/fsm_managing_event_handler.py new file mode 100644 index 0000000..69204b1 --- /dev/null +++ b/lib/tfw/components/fsm_managing_event_handler.py @@ -0,0 +1,88 @@ +# Copyright (C) 2018 Avatao.com Innovative Learning Kft. +# All Rights Reserved. See LICENSE file for details. + +from collections import defaultdict + +from tfw import BroadcastingEventHandler +from tfw.config.logs import logging + +LOG = logging.getLogger(__name__) + + +class FSMManagingEventHandler(BroadcastingEventHandler): + def __init__(self, key, fsm_type): + super().__init__(key) + self.fsm = fsm_type() + self.fsm_manager = FSMManager(self.fsm) + self._fsm_updater = FSMUpdater(self.fsm) + + self.command_handlers = { + 'trigger': self.handle_trigger, + 'update': self.handle_update + } + + def handle_event(self, message): + try: + data = message['data'] + message['data'] = self.command_handlers[data['command']](data) + return message + except KeyError: + LOG.error('IGNORING MESSAGE: Invalid message received: %s', message) + + def handle_trigger(self, data): + self.fsm_manager.trigger(data['value']) + return self.with_fsm_update(data) + + def with_fsm_update(self, data): + return { + **data, + **self._fsm_updater.get_fsm_state_and_transitions() + } + + def handle_update(self, data): + return self.with_fsm_update(data) + + +class FSMManager: + def __init__(self, fsm): + self.fsm = fsm + self.trigger_predicates = defaultdict(list) + + def trigger(self, trigger): + predicate_results = [ + predicate() + for predicate in self.trigger_predicates[trigger] + ] + + # TODO: think about what could we do when this prevents triggering + if all(predicate_results): + try: + self.fsm.trigger(trigger) + except AttributeError: + LOG.debug('FSM failed to execute nonexistent trigger: "%s"', trigger) + + def subscribe_predicate(self, trigger, *predicates): + self.trigger_predicates[trigger].extend(predicates) + + def unsubscribe_predicate(self, trigger, *predicates): + self.trigger_predicates[trigger] = [ + predicate + for predicate in self.trigger_predicates[trigger] + not in predicates + ] + + +class FSMUpdater: + def __init__(self, fsm): + self.fsm = fsm + + def get_fsm_state_and_transitions(self): + state = self.fsm.state + valid_transitions = [ + {'trigger': trigger} + for trigger in self.fsm.machine.get_triggers(self.fsm.state) + ] + return { + 'current_state': state, + 'valid_transitions': valid_transitions + } diff --git a/lib/tfw/networking/server/tfw_server.py b/lib/tfw/networking/server/tfw_server.py index 7c34c05..bdc5179 100644 --- a/lib/tfw/networking/server/tfw_server.py +++ b/lib/tfw/networking/server/tfw_server.py @@ -1,11 +1,8 @@ # Copyright (C) 2018 Avatao.com Innovative Learning Kft. # All Rights Reserved. See LICENSE file for details. -from collections import defaultdict - from tornado.web import Application -from tfw.networking import MessageSender from tfw.networking.event_handlers import ServerUplinkConnector from tfw.networking.server import EventHandlerConnector from tfw.config.logs import logging @@ -18,110 +15,29 @@ class TFWServer: """ This class handles the proxying of messages between the frontend and event handers. It proxies messages from the "/ws" route to all event handlers subscribed to a ZMQ - SUB socket. It also manages an FSM you can define as a constructor argument. + SUB socket. """ - def __init__(self, fsm_type): - """ - :param fsm_type: the type of FSM you want TFW to use - """ - self._fsm = fsm_type() - self._fsm_updater = FSMUpdater(self._fsm) - self._fsm_manager = FSMManager(self._fsm) - self._fsm.subscribe_callback(self._fsm_updater.update) + def __init__(self): self._event_handler_connector = EventHandlerConnector() + self._uplink_connector = ServerUplinkConnector() self.application = Application([( r'/ws', ZMQWebSocketProxy,{ 'event_handler_connector': self._event_handler_connector, - 'message_handlers': [self.append_fsm_data, self.handle_trigger] + 'message_handlers': [self.handle_trigger] })] ) - # self.controller_responder = ControllerResponder(self.fsm) - # TODO: add this once controller stuff is resolved - - @property - def fsm(self): - return self._fsm - - @property - def fsm_manager(self): - return self._fsm_manager - - def append_fsm_data(self, message): - message['FSMUpdate'] = self._fsm_updater.get_fsm_state_and_transitions() - return message def handle_trigger(self, message): - LOG.debug('Executing handler for trigger "%s"', message.get('trigger', '')) - self.trigger_fsm(message) - - def trigger_fsm(self, message): - trigger = message.get('trigger', '') - try: - self._fsm_manager.trigger(trigger, message) - except AttributeError: - LOG.debug('FSM failed to execute nonexistent trigger: "%s"', trigger) + if 'trigger' in message: + LOG.debug('Executing handler for trigger "%s"', message.get('trigger', '')) + self._uplink_connector.send_to_eventhandler({ + 'key': 'fsm', + 'data': { + 'command': 'trigger', + 'value': message.get('trigger', '') + } + }) def listen(self, port): self.application.listen(port) - - -class FSMManager: - def __init__(self, fsm): - self._fsm = fsm - self.trigger_predicates = defaultdict(list) - self.messenge_sender = MessageSender() - - @property - def fsm(self): - return self._fsm - - def trigger(self, trigger, message): - predicate_results = [] - for predicate in self.trigger_predicates[trigger]: - success, message = predicate(message) - predicate_results.append(success) - self.messenge_sender.send('FSM', message) - - if all(predicate_results): - try: - self.fsm.trigger(trigger, message=message) - except AttributeError: - LOG.debug('FSM failed to execute nonexistent trigger: "%s"', trigger) - - def subscribe_predicate(self, trigger, *predicates): - self.trigger_predicates[trigger].extend(predicates) - - def unsubscribe_predicate(self, trigger, *predicates): - self.trigger_predicates[trigger] = [ - predicate - for predicate in self.trigger_predicates[trigger] - not in predicates - ] - - -class FSMUpdater: - def __init__(self, fsm): - self.fsm = fsm - self.uplink = ServerUplinkConnector() - - def update(self, kwargs_dict): - # pylint: disable=unused-argument - self.uplink.send(self.generate_fsm_update()) - - def generate_fsm_update(self): - return { - 'key': 'FSMUpdate', - 'data': self.get_fsm_state_and_transitions() - } - - def get_fsm_state_and_transitions(self): - state = self.fsm.state - valid_transitions = [ - {'trigger': trigger} - for trigger in self.fsm.machine.get_triggers(self.fsm.state) - ] - return { - 'current_state': state, - 'valid_transitions': valid_transitions - }