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 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 #Cached objects self._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 self.enterState(self._tutorial.INIT) def 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) def _teardownState(self): if self._state is None: #No state, no teardown return #Clear the current actions for action in self._actions.values(): self._pM.uninstall(action) self._actions = {} #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) transitions = self._tutorial.get_transition_dict(self._state) for (event, next_state) in transitions.values(): if isinstance(event, AutomaticTransitionEvent): state_name = next_state break self._sEvents.add(self._pM.subscribe(event, save_args(self._handleEvent, next_state))) for action in self._actions.values(): self._pM.install(action) 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 """ self.setCurrentActivity() #Temp Hack until activity in events/actions # Recursive base case if state_name == self._state: #Nothing to do 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: self.stop() self._tutorial = TutorialRunner(Vault.loadTutorial(tutorialID), self._probeManager) #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")