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
201
202
203
204
205
206
207
|
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("sugar.tutorius.engine")
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 = {}
self._installed_actions = {}
self._install_errors = {}
#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_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()
#Clear the EventFilters
for event in self._sEvents:
self._pM.unsubscribe(event)
self._sEvents.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
# 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():
# 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 __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 _all_actions_installed(self):
transitions = self._tutorial.get_transition_dict(self._state)
if len(transitions) == 0:
return
for (event, next_state) in transitions.values():
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")
# 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
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
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()
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()
#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")
|