Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/tutorius/engine.py
blob: e1024067a07738fcc81cfe51a3acfafd38cdfe18 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# 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")