# 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 2 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 import dbus.mainloop.glib from jarabe.model import shell from sugar.bundle.activitybundle import ActivityBundle from .vault import Vault from .TProbe import ProbeManager from .dbustools import save_args from .tutorial import Tutorial, AutomaticTransitionEvent from .translator import ResourceTranslator LOGGER = logging.getLogger("TutorialRunner") class TutorialRunner(object): """ Driver for the execution of one tutorial """ def __init__(self, tutorial, probeManager): """Constructor @param tutorial Tutorial to execute @param probeManager probeManager to use """ self._tutorial = tutorial self._pM = probeManager #State self._state = None self._sEvents = set() #Subscribed Events self._installed_actions = {} #Temp FIX until event/actions have an activity id self._activity_id = None #Temp FIX until event, actions have an activity id def setCurrentActivity(self): self._pM.currentActivity = self._activity_id def start(self): self.setCurrentActivity() #Temp Hack until activity in events/actions LOGGER.debug("Starting tutorial : %s"%self._tutorial.name) self.enterState(self._tutorial.INIT) def stop(self): self.setCurrentActivity() #Temp Hack until activity in events/actions LOGGER.debug("Stopping tutorial execution for tutorial : %s"%self._tutorial.name) self.enterState(self._tutorial.END) self._teardownState() self._state = None LOGGER.debug("Teardown complete for tutorial : %s"%self._tutorial.name) def _handleEvent(self, next_state, event): #FIXME sanity check, log event that was not installed and ignore self.enterState(next_state) def _teardownState(self): if self._state is None: #No state, no teardown return #Clear the current actions for action_address in self._installed_actions.values(): self._pM.uninstall(action_address) self._installed_actions = {} #Clear the EventFilters for event in self._sEvents: self._pM.unsubscribe(event) self._sEvents.clear() def __save_address(self, action_name, action_address): self._installed_actions[action_name] = action_address def _setupState(self): if self._state is None: raise RuntimeError("Attempting to setupState without a state") # Handle the automatic event state_name = self._state actions = self._tutorial.get_action_dict(self._state) transitions = self._tutorial.get_transition_dict(self._state) # Verify if we have an automatic transition in the state - if so, we # will skip installing the actions and events and go straight to the # next state for (event, next_state) in transitions.values(): if isinstance(event, AutomaticTransitionEvent): state_name = next_state return state_name # Install all the actions first for (action_name, action) in actions.items(): self._pM.install(action, save_args(self.__save_address, action_name)) # Install the event filters for (event, next_state) in transitions.values(): self._sEvents.add(self._pM.subscribe(event, save_args(self._handleEvent, next_state))) return state_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. When entering the state, 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 """ LOGGER.debug("Tutorial %s moving into state : %s"%(self._tutorial.name, state_name)) self.setCurrentActivity() #Temp Hack until activity in events/actions # Recursive base case if state_name == self._state: #Nothing to do LOGGER.debug("Installation of state %s completed"%state_name) return 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()) class Engine: """ Driver for the execution of tutorials """ def __init__(self, probeManager=None): """Constructor @param probeManager (optional) ProbeManager instance to use """ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) #FIXME shell.get_model() will only be useful in the shell process self._shell = shell.get_model() self._probeManager = probeManager or ProbeManager() self._tutorial = None def launch(self, tutorialID): """ Launch a tutorial @param tutorialID unique tutorial identifier used to retrieve it from the disk """ if self._tutorial: LOGGER.debug("Stopping old tutorial : %s"%self._tutorial.name) self.stop() LOGGER.debug("Starting new tutorial with ID : %s"%str(tutorialID)) # Insert the resource translation layer into the translator_layer = ResourceTranslator(self._probeManager, tutorialID) self._tutorial = TutorialRunner(Vault.loadTutorial(tutorialID), translator_layer) #Get the active activity from the shell activity = self._shell.get_active_activity() #TProbes automatically use the bundle id, available from the ActivityBundle bundle = ActivityBundle(activity.get_bundle_path()) self._tutorial._activity_id = bundle.get_bundle_id() #HACK until we have activity id's in action/events self._tutorial.start() def stop(self, tutorialID=None): """ Stop the current tutorial """ if tutorialID is None: logging.warning( "stop() without a tutorialID will become deprecated") self._tutorial.stop() self._tutorial = None def pause(self, tutorialID=None): """ Interrupt the current tutorial and save its state in the journal """ if tutorialID is None: logging.warning( \ "pause() without a tutorialID will become deprecated") raise NotImplementedError("Unable to store tutorial state")