From 2c8fe66c0f7490c8aaaae27b4977b987001c6b71 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 01 Dec 2009 20:11:43 +0000 Subject: Merge branch 'master' of git://git.sugarlabs.org/tutorius/simpoirs-clone Conflicts: src/extensions/tutoriusremote.py tutorius/TProbe.py tutorius/creator.py --- (limited to 'tutorius/engine.py') diff --git a/tutorius/engine.py b/tutorius/engine.py index c945e49..198fa11 100644 --- a/tutorius/engine.py +++ b/tutorius/engine.py @@ -1,4 +1,21 @@ +# Copyright (C) 2009, Tutorius.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 1 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + import logging +from heapq import heappush, heappop import dbus.mainloop.glib from jarabe.model import shell from sugar.bundle.activitybundle import ActivityBundle @@ -7,7 +24,21 @@ from .vault import Vault from .TProbe import ProbeManager from .dbustools import save_args from .tutorial import Tutorial, AutomaticTransitionEvent +from .translator import ResourceTranslator + +# Priority values for queuable messages +STOP_MSG_PRIORITY = 5 +EVENT_NOTIFICATION_MSG_PRIORITY = 10 +# List of runner states +RUNNER_STATE_IDLE = "idle" +RUNNER_STATE_SETUP_ACTIONS = "setup_actions" +RUNNER_STATE_SETUP_EVENTS = "setup_events" +RUNNER_STATE_AWAITING_NOTIFICATIONS = "awaiting_notification" +RUNNER_STATE_UNINSTALLING_ACTIONS = "uninstalling_actions" +RUNNER_STATE_UNSUBSCRIBING_EVENTS = "unsubscribing_events" + +LOGGER = logging.getLogger("sugar.tutorius.engine") class TutorialRunner(object): """ @@ -21,12 +52,27 @@ class TutorialRunner(object): self._tutorial = tutorial self._pM = probeManager + # The tutorial runner's state. For more details, see : + # https://docs.google.com/Doc?docid=0AVT_nzmWT2B2ZGN3dDd2MzRfNTBka3J4bW1kaA&hl=en + self._runner_state = RUNNER_STATE_IDLE + + # The message queue is a heap, so only heap operations should be done + # on it like heappush, heappop, etc... + # The stocked messages are actually a list of parameters that should be + # passed to the appropriate function. E.g. When raising an event notification, + # it saves the (next_state, event) in the message. + self._message_queue = [] + #State self._state = None - self._sEvents = set() #Subscribed Events #Cached objects - self._actions = {} + self._installed_actions = {} + self._installation_errors = {} + + # Subscribed Events + self._subscribed_events = {} + self._subscription_errors = {} #Temp FIX until event/actions have an activity id self._activity_id = None @@ -34,86 +80,216 @@ class TutorialRunner(object): #Temp FIX until event, actions have an activity id def setCurrentActivity(self): self._pM.currentActivity = self._activity_id - + + ########################################################################### + # Incoming messages def start(self): self.setCurrentActivity() #Temp Hack until activity in events/actions self.enterState(self._tutorial.INIT) def stop(self): + if self._runner_state == RUNNER_STATE_SETUP_ACTIONS or \ + self._runner_state == RUNNER_STATE_SETUP_EVENTS: + heappush(self._message_queue, (STOP_MSG_PRIORITY, None)) + elif self._runner_state != RUNNER_STATE_IDLE: + self._execute_stop() + + def action_installed(self, action_name, address): + LOGGER.debug("TutorialRunner :: Action %s received address %s"%(action_name, address)) + self._installed_actions[action_name] = address + # Verify if we just completed the installation of the actions for this state + self._verify_action_install_state() + + def install_error(self, action_name, action, exception): + # TODO : Fix this as it doesn't warn the user about the problem or anything + LOGGER.debug("TutorialRunner :: Action could not be installed %s, exception was : %s"%(str(action), str(exception))) + self._installation_errors[action_name] = exception + self._verify_action_install_state() + + def event_subscribed(self, event_name, event_address): + LOGGER.debug("TutorialRunner :: Event %s was subscribed to, located at address %s"%(event_name, event_address)) + self._subscribed_events[event_name] = event_address + + # Verify if we just completed the subscription of all the events for this state + self._verify_event_install_state() + + def subscribe_error(self, event_name, exception): + # TODO : Do correct error handling here + LOGGER.debug("TutorialRunner :: Could not subscribe to event %s, got exception : %s"%(event_name, str(exception))) + self._subscription_errors[event_name] = exception + + # Verify if we just completed the subscription of all the events for this state + self._verify_event_install_state() + + def all_actions_installed(self): + self._runner_state = RUNNER_STATE_SETUP_EVENTS + # Process the messages that might have been stored + self._process_pending_messages() + + # If we processed a message that changed the runner state, we need to stop + # processing + if self._runner_state != RUNNER_STATE_SETUP_EVENTS: + return + + # Start subscribing to events + transitions = self._tutorial.get_transition_dict(self._state) + + # If there are no transitions, raise the All Events Subscribed message + if len(transitions) == 0: + self.all_events_subscribed() + return + + # Send all the event registration + for (event_name, (event, next_state)) in transitions.items(): + self._pM.subscribe(event, + save_args(self._handleEvent, next_state), + save_args(self.event_subscribed, event_name), + save_args(self.subscribe_error, event_name)) + + def all_events_subscribed(self): + self._runner_state = RUNNER_STATE_AWAITING_NOTIFICATIONS + self._process_pending_messages() + + ########################################################################### + # Helper functions + def _execute_stop(self): self.setCurrentActivity() #Temp Hack until activity in events/actions - self.enterState(self._tutorial.END) self._teardownState() self._state = None + self._runner_state = RUNNER_STATE_IDLE def _handleEvent(self, next_state, event): - #FIXME sanity check, log event that was not installed and ignore - self.enterState(next_state) + # Look if we are actually receiving notifications + if self._runner_state == RUNNER_STATE_AWAITING_NOTIFICATIONS: + LOGGER.debug("TutorialRunner :: Received event notification in AWAITING_NOTIFICATIONS for %s"%str(event)) + transitions = self._tutorial.get_transition_dict(self._state) + for (this_event, this_next_state_name) in transitions.values(): + if event == this_event and next_state == this_next_state_name: + self.enterState(next_state) + break + elif self._runner_state == RUNNER_STATE_SETUP_EVENTS: + LOGGER.debug("TutorialRunner :: Queuing event notification to go to state %s"%next_state) + # Push the message on the queue + heappush(self._message_queue, (EVENT_NOTIFICATION_MSG_PRIORITY, (next_state, event))) + # Ignore the message for all other states def _teardownState(self): if self._state is None: #No state, no teardown return + self._remove_installed_actions() + self._remove_subscribed_events() + def _remove_installed_actions(self): #Clear the current actions - for action in self._actions.values(): - self._pM.uninstall(action) - self._actions = {} + for (action_name, action_address) in self._installed_actions.items(): + LOGGER.debug("TutorialRunner :: Uninstalling action %s with address %s"%(action_name, action_address)) + self._pM.uninstall(action_address) + self._installed_actions.clear() + self._installation_errors.clear() + def _remove_subscribed_events(self): #Clear the EventFilters - for event in self._sEvents: - self._pM.unsubscribe(event) - self._sEvents.clear() - - def _setupState(self): - if self._state is None: - raise RuntimeError("Attempting to setupState without a state") - - # Handle the automatic event - state_name = self._state - - self._actions = self._tutorial.get_action_dict(self._state) + for (event_name, event_address) in self._subscribed_events.items(): + self._pM.unsubscribe(event_address) + self._subscribed_events.clear() + self._subscription_errors.clear() + + def _verify_action_install_state(self): + actions = self._tutorial.get_action_dict(self._state) + + # Do the check to see if we have finished installing all the actions by either having + # received a address for it or an error message + install_complete = True + for (this_action_name, this_action) in actions.items(): + if not this_action_name in self._installed_actions.keys() and \ + not this_action_name in self._installation_errors.keys(): + # There's at least one uninstalled action, so we still wait + install_complete = False + break + + if install_complete: + LOGGER.debug("TutorialRunner :: All actions installed!") + # Raise the All Actions Installed event for the TutorialRunner state + self.all_actions_installed() + + def _verify_event_install_state(self): transitions = self._tutorial.get_transition_dict(self._state) - for (event, next_state) in transitions.values(): - if isinstance(event, AutomaticTransitionEvent): - state_name = next_state + # Check to see if we completed all the event subscriptions + subscribe_complete = True + for (this_event_name, (this_event, next_state)) in transitions.items(): + if not this_event_name in self._subscribed_events.keys() and \ + not this_event_name in self._subscription_errors.keys(): + subscribe_complete = False break + + if subscribe_complete: + LOGGER.debug("TutorialRunner : Subscribed to all events!") + self.all_events_subscribed() + + def _process_pending_messages(self): + while len(self._message_queue) != 0: + (priority, message) = heappop(self._message_queue) + + if priority == STOP_MSG_PRIORITY: + LOGGER.debug("TutorialRunner :: Stop message taken from message queue") + # We can safely ignore the rest of the events + self._message_queue = [] + self._execute_stop() + elif priority == EVENT_NOTIFICATION_MSG_PRIORITY: + LOGGER.debug("TutorialRunner :: Handling stored event notification for next_state %s"%message[0]) + self._handle_event(*message) - self._sEvents.add(self._pM.subscribe(event, save_args(self._handleEvent, next_state))) + def _setupState(self): + if self._state is None: + raise RuntimeError("Attempting to setupState without a state") - for action in self._actions.values(): - self._pM.install(action) + actions = self._tutorial.get_action_dict(self._state) + + if len(actions) == 0: + self.all_actions_installed() + return - return state_name + for (action_name, action) in actions.items(): + LOGGER.debug("TutorialRunner :: Installed action %s"%(action_name)) + self._pM.install(action, + save_args(self.action_installed, action_name), + save_args(self.install_error, action_name)) def enterState(self, state_name): """ - Starting from the state_name, the runner execute states until - no automatic transition are found and will wait for an external - event to occur. + Starting from the state_name, the runner execute states from the + tutorial until no automatic transitions are found and will wait + for an external event to occur. - When entering the state, actions and events from the previous + When entering the sate, actions and events from the previous state are respectively uninstalled and unsubscribed and actions and events from the state_name will be installed and subscribed. @param state_name The name of the state to enter in """ self.setCurrentActivity() #Temp Hack until activity in events/actions + # Set the runner state to actions setup + self._runner_state = RUNNER_STATE_SETUP_ACTIONS - # Recursive base case - if state_name == self._state: - #Nothing to do - return + real_next_state = None + skip_to_state = state_name + + # As long as we do have automatic transitions, skip them to go to the + # next state + while skip_to_state != real_next_state: + real_next_state = skip_to_state + transitions = self._tutorial.get_transition_dict(skip_to_state) + for (event, next_state) in transitions.values(): + if isinstance(event, AutomaticTransitionEvent): + skip_to_state = next_state + break self._teardownState() - self._state = state_name - - # Recursively call the enterState in case there was an automatic - # transition in the state definition - self.enterState(self._setupState()) - - + self._state = real_next_state + self._setupState() class Engine: """ @@ -137,7 +313,8 @@ class Engine: if self._tutorial: self.stop() - self._tutorial = TutorialRunner(Vault.loadTutorial(tutorialID), self._probeManager) + translator_decorator = ResourceTranslator(self._probeManager, tutorialID) + self._tutorial = TutorialRunner(Vault.loadTutorial(tutorialID), translator_decorator) #Get the active activity from the shell activity = self._shell.get_active_activity() -- cgit v0.9.1