Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/tutorius/engine.py
diff options
context:
space:
mode:
Diffstat (limited to 'tutorius/engine.py')
-rw-r--r--tutorius/engine.py209
1 files changed, 170 insertions, 39 deletions
diff --git a/tutorius/engine.py b/tutorius/engine.py
index bb2453a..b8fe988 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
@@ -9,6 +26,18 @@ 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 = 0
+RUNNER_STATE_SETUP_ACTIONS = 1
+RUNNER_STATE_SETUP_EVENTS = 2
+RUNNER_STATE_AWAITING_NOTIFICATIONS = 3
+RUNNER_STATE_UNINSTALLING_ACTIONS = 4
+RUNNER_STATE_UNSUBSCRIBING_EVENTS = 5
+
LOGGER = logging.getLogger("sugar.tutorius.engine")
class TutorialRunner(object):
@@ -23,14 +52,25 @@ 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...
+ self._message_queue = []
+
#State
self._state = None
- self._sEvents = set() #Subscribed Events
#Cached objects
self._actions = {}
self._installed_actions = {}
- self._install_errors = {}
+ self._installation_errors = {}
+
+ # Subscribed Events
+ self._subscribed_events = {}
+ self._subscription_errors = {}
#Temp FIX until event/actions have an activity id
self._activity_id = None
@@ -44,42 +84,70 @@ class TutorialRunner(object):
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))
+ else:
+ self._execute_stop()
+
+ def _execute_stop(self):
self.setCurrentActivity() #Temp Hack until activity in events/actions
self.enterState(self._tutorial.END)
self._teardownState()
self._state = None
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)
+ 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
-
+
#Clear the current 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._actions = {}
self._installed_actions.clear()
- self._install_errors.clear()
+ self._installation_errors.clear()
#Clear the EventFilters
- for event in self._sEvents:
+ for (event_name, event) in self._subscribed_events.items():
self._pM.unsubscribe(event)
- self._sEvents.clear()
+ self._subscribed_events.clear()
+ self._subscription_errors.clear()
def __action_installed(self, action_name, action, 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 __verify_action_install_state(self):
# 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 self._actions.items():
- if not this_action_name in self._installed_actions.keys() and not this_action in self._install_errors.keys():
+ if not this_action_name in self._installed_actions.keys() and \
+ not this_action in self._installation_errors.keys():
# There's at least one uninstalled action, so we still wait
install_complete = False
break
@@ -87,46 +155,101 @@ class TutorialRunner(object):
if install_complete:
LOGGER.debug("TutorialRunner :: All actions installed!")
# Raise the All Actions Installed event for the TutorialRunner state
- self._all_actions_installed()
+ self.__all_actions_installed()
- 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._install_errors[action_name] = exception
+ 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 : "%(event_name, str(exception)))
+ self._subscribed_error[event_name] = exception
- def _all_actions_installed(self):
+ # Verify if we just completed the subscription of all the events for this state
+ self.__verify_event_install_state()
+
+ def __verify_event_install_state(self):
transitions = self._tutorial.get_transition_dict(self._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 in self._subscribed_events.keys() and \
+ not this_event in self._subscription_errors.keys():
+ subscribe_complete = False
+ break
+
+ if subscribe_complete:
+ LOGGER.debug("TutorialRunner : Subscribed to all events!")
+ self.__all_events_subscribed()
+
+ 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, next_state) in transitions.values():
- self._sEvents.add(self._pM.subscribe(event, save_args(self._handleEvent, next_state)))
+ self._pM.subscribe(event,
+ save_args(self._handleEvent, next_state),
+ save_args(self.__event_subscribed, event),
+ save_args(self.__subscribe_error, event))
+
+ def __all_events_subscribed(self):
+ self._runner_state = RUNNER_STATE_AWAITING_NOTIFICATIONS
+ self.__process_pending_messages()
+
+ 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()
+ # Start removing the installed addons
+ #if self._runner_state == RUNNER_STATE_AWAITING_NOTIFICATIONS:
+ # # Start uninstalling the events
+ # self.__unsubscribe_events()
+ #if self._runner_state == RUNNER_STATE_SETUP_EVENTS:
+ # self.__uninstall_actions()
+ elif priority == EVENT_NOTIFICATION_MSG_PRIORITY:
+ LOGGER.debug("TutorialRunner :: Handling stored event notification for next_state %s"%message[0])
+ self._handle_event(*message)
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)
- transitions = self._tutorial.get_transition_dict(self._state)
-
- for (event, next_state) in transitions.values():
- if isinstance(event, AutomaticTransitionEvent):
- state_name = next_state
- return state_name
if len(self._actions) == 0:
- self._all_actions_installed()
- return state_name
+ self.__all_actions_installed()
+ return
for (action_name, action) in self._actions.items():
- self._pM.install(action, save_args(self.__action_installed, action_name),
- save_args(self.__install_error, action_name))
LOGGER.debug("TutorialRunner :: Installed action %s"%(action_name))
-
- return state_name
+ self._pM.install(action,
+ save_args(self.__action_installed, action_name),
+ save_args(self.__install_error, action_name))
def enterState(self, state_name):
"""
@@ -141,18 +264,26 @@ class TutorialRunner(object):
@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
+ self._state = real_next_state
- # Recursively call the enterState in case there was an automatic
- # transition in the state definition
- self.enterState(self._setupState())
+ self._setupState()
class Engine:
"""