aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helpers/action.py102
-rw-r--r--helpers/mapping.py4
-rw-r--r--helpers/music_file.py22
3 files changed, 109 insertions, 19 deletions
diff --git a/helpers/action.py b/helpers/action.py
index ec8fcb6..a6c48e9 100644
--- a/helpers/action.py
+++ b/helpers/action.py
@@ -1,10 +1,11 @@
1import threading 1import threading
2import time 2import time
3 3
4from . import debug_print 4from transitions.extensions import HierarchicalMachine as Machine
5from . import debug_print, error_print
5 6
6class Action: 7class Action:
7 action_types = [ 8 ACTION_TYPES = [
8 'command', 9 'command',
9 'interrupt_wait', 10 'interrupt_wait',
10 'pause', 11 'pause',
@@ -17,40 +18,106 @@ class Action:
17 'wait', 18 'wait',
18 ] 19 ]
19 20
21 STATES = [
22 'initial',
23 'loading',
24 'failed',
25 {
26 'name': 'loaded',
27 'children': ['running']
28 }
29 ]
30
31 TRANSITIONS = [
32 {
33 'trigger': 'load',
34 'source': 'initial',
35 'dest': 'loading'
36 },
37 {
38 'trigger': 'fail',
39 'source': 'loading',
40 'dest': 'failed'
41 },
42 {
43 'trigger': 'success',
44 'source': 'loading',
45 'dest': 'loaded'
46 },
47 {
48 'trigger': 'run',
49 'source': 'loaded',
50 'dest': 'loaded_running',
51 'after': 'finish_action'
52 },
53 {
54 'trigger': 'interrupt',
55 'source': 'loaded_running',
56 'dest': 'loaded',
57 'before': 'trigger_interrupt'
58 },
59 {
60 'trigger': 'finish_action',
61 'source': 'loaded_running',
62 'dest': 'loaded'
63 }
64 ]
65
20 def __init__(self, action, key, **kwargs): 66 def __init__(self, action, key, **kwargs):
21 if action in self.action_types: 67 Machine(model=self, states=self.STATES,
22 self.action = action 68 transitions=self.TRANSITIONS, initial='initial',
23 else: 69 ignore_invalid_triggers=True, queued=True)
24 raise Exception("Unknown action {}".format(action))
25 70
71 self.action = action
26 self.key = key 72 self.key = key
27 self.mapping = key.parent 73 self.mapping = key.parent
28 self.arguments = kwargs 74 self.arguments = kwargs
29 self.sleep_event = None 75 self.sleep_event = None
76 self.waiting_music = None
77 self.load()
30 78
31 def ready(self): 79 def ready(self):
32 if 'music' in self.arguments: 80 return self.is_loaded(allow_substates=True)
33 return self.arguments['music'].is_loaded(allow_substates=True) 81
82 def callback_loaded(self, success):
83 if success:
84 self.success()
85 else:
86 self.fail()
87
88 # Machine states / events
89 def on_enter_loading(self):
90 if self.action in self.ACTION_TYPES:
91 if 'music' in self.arguments:
92 self.arguments['music'].subscribe_loaded(self.callback_loaded)
93 else:
94 self.success()
34 else: 95 else:
35 return True 96 error_print("Unknown action {}".format(self.action))
97 self.fail()
98
36 99
37 def run(self): 100 def on_enter_loaded_running(self):
38 debug_print(self.description()) 101 debug_print(self.description())
39 getattr(self, self.action)(**self.arguments) 102 getattr(self, self.action)(**self.arguments)
40 103
41 def description(self): 104 def trigger_interrupt(self):
42 return getattr(self, self.action + "_print")(**self.arguments)
43
44 def interrupt(self):
45 if getattr(self, self.action + "_interrupt", None): 105 if getattr(self, self.action + "_interrupt", None):
46 return getattr(self, self.action + "_interrupt")(**self.arguments) 106 return getattr(self, self.action + "_interrupt")(**self.arguments)
47 107
108 # Helpers
48 def music_list(self, music): 109 def music_list(self, music):
49 if music is not None: 110 if music is not None:
50 return [music] 111 return [music]
51 else: 112 else:
52 return self.mapping.open_files.values() 113 return self.mapping.open_files.values()
53 114
115 def description(self):
116 if getattr(self, self.action + "_print", None):
117 return getattr(self, self.action + "_print")(**self.arguments)
118 else:
119 return "unknown action {}".format(self.action)
120
54 # Actions 121 # Actions
55 def command(self, command="", **kwargs): 122 def command(self, command="", **kwargs):
56 # FIXME: todo 123 # FIXME: todo
@@ -104,6 +171,7 @@ class Action:
104 music.stop(fade_out=fade_out) 171 music.stop(fade_out=fade_out)
105 172
106 if previous is not None: 173 if previous is not None:
174 self.waiting_music = previous
107 previous.stop( 175 previous.stop(
108 fade_out=fade_out, 176 fade_out=fade_out,
109 wait=wait, 177 wait=wait,
@@ -254,10 +322,14 @@ class Action:
254 322
255 return message 323 return message
256 324
257 # Interruptions 325 # Interruptions (only for non-"atomic" actions)
258 def wait_interrupt(self, duration=0, music=None, **kwargs): 326 def wait_interrupt(self, duration=0, music=None, **kwargs):
259 if self.sleep_event is not None: 327 if self.sleep_event is not None:
260 self.sleep_event.set() 328 self.sleep_event.set()
261 if music is not None: 329 if music is not None:
262 music.wait_event.set() 330 music.wait_event.set()
263 331
332 def stop_interrupt(self, music=None, fade_out=0, wait=False,
333 set_wait_id=None, **kwargs):
334 if self.waiting_music is not None:
335 self.waiting_music.wait_event.set()
diff --git a/helpers/mapping.py b/helpers/mapping.py
index c2a94e6..b71f3fe 100644
--- a/helpers/mapping.py
+++ b/helpers/mapping.py
@@ -178,8 +178,8 @@ class Mapping(RelativeLayout):
178 **config['music_properties'][filename]) 178 **config['music_properties'][filename])
179 else: 179 else:
180 seen_files[filename] = MusicFile( 180 seen_files[filename] = MusicFile(
181 self, 181 filename,
182 filename) 182 self)
183 183
184 if filename not in key_properties[mapped_key]['files']: 184 if filename not in key_properties[mapped_key]['files']:
185 key_properties[mapped_key]['files'] \ 185 key_properties[mapped_key]['files'] \
diff --git a/helpers/music_file.py b/helpers/music_file.py
index ccf60ce..aeba1b9 100644
--- a/helpers/music_file.py
+++ b/helpers/music_file.py
@@ -32,7 +32,8 @@ class MusicFile:
32 { 32 {
33 'trigger': 'load', 33 'trigger': 'load',
34 'source': 'initial', 34 'source': 'initial',
35 'dest': 'loading' 35 'dest': 'loading',
36 'after': 'poll_loaded'
36 }, 37 },
37 { 38 {
38 'trigger': 'fail', 39 'trigger': 'fail',
@@ -68,7 +69,8 @@ class MusicFile:
68 'trigger': 'stopped', 69 'trigger': 'stopped',
69 'source': '*', 70 'source': '*',
70 'dest': 'loaded', 71 'dest': 'loaded',
71 'before': 'trigger_stopped_events' 72 'before': 'trigger_stopped_events',
73 'conditions': ['is_in_use']
72 } 74 }
73 ] 75 ]
74 76
@@ -77,6 +79,7 @@ class MusicFile:
77 transitions=self.TRANSITIONS, initial='initial', 79 transitions=self.TRANSITIONS, initial='initial',
78 ignore_invalid_triggers=True) 80 ignore_invalid_triggers=True)
79 81
82 self.loaded_callbacks = []
80 self.mapping = mapping 83 self.mapping = mapping
81 self.filename = filename 84 self.filename = filename
82 self.name = name or filename 85 self.name = name or filename
@@ -230,6 +233,21 @@ class MusicFile:
230 self.wait_event.clear() 233 self.wait_event.clear()
231 self.wait_event.wait() 234 self.wait_event.wait()
232 235
236 # Let other subscribe for an event when they are ready
237 def subscribe_loaded(self, callback):
238 with file_lock:
239 if self.is_loaded(allow_substates=True):
240 callback(True)
241 elif self.is_failed():
242 callback(False)
243 else:
244 self.loaded_callbacks.append(callback)
245
246 def poll_loaded(self):
247 for callback in self.loaded_callbacks:
248 callback(self.is_loaded())
249 self.loaded_callbacks = []
250
233 # Callbacks 251 # Callbacks
234 def finished_callback(self): 252 def finished_callback(self):
235 self.stopped() 253 self.stopped()