]> git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/blob - music_sampler/action.py
Cleanup key and action workflows
[perso/Immae/Projets/Python/MusicSampler.git] / music_sampler / action.py
1 from transitions.extensions import HierarchicalMachine as Machine
2 from .helpers import debug_print, error_print
3 from . import actions
4
5 class Action:
6 STATES = [
7 'initial',
8 'loading',
9 'failed',
10 {
11 'name': 'loaded',
12 'children': ['stopped', 'running']
13 },
14 'destroyed'
15 ]
16
17 TRANSITIONS = [
18 {
19 'trigger': 'load',
20 'source': 'initial',
21 'dest': 'loading'
22 },
23 {
24 'trigger': 'fail',
25 'source': ['loading', 'loaded'],
26 'dest': 'failed',
27 },
28 {
29 'trigger': 'success',
30 'source': 'loading',
31 'dest': 'loaded_stopped',
32 },
33 {
34 'trigger': 'reload',
35 'source': 'loaded',
36 'dest': 'loading',
37 },
38 {
39 'trigger': 'run',
40 'source': 'loaded_stopped',
41 'dest': 'loaded_running',
42 'after': 'finish_action',
43 },
44 {
45 'trigger': 'finish_action',
46 'source': 'loaded_running',
47 'dest': 'loaded_stopped'
48 },
49 {
50 'trigger': 'destroy',
51 'source': '*',
52 'dest': 'destroyed'
53 }
54 ]
55
56 def __init__(self, action, key, **kwargs):
57 Machine(model=self, states=self.STATES,
58 transitions=self.TRANSITIONS, initial='initial',
59 ignore_invalid_triggers=True, queued=True,
60 after_state_change=self.notify_state_change)
61
62 self.action = action
63 self.key = key
64 self.mapping = key.parent
65 self.arguments = kwargs
66 self.sleep_event = None
67 self.waiting_music = None
68
69 def is_loaded_or_failed(self):
70 return self.is_loaded(allow_substates=True) or self.is_failed()
71
72 def callback_music_state(self, new_state):
73 # If a music gets unloaded while the action is loaded_running and
74 # depending on the music, it won't be able to do the finish_action.
75 # Can that happen?
76 # a: play 'mp3';
77 # z: wait 'mp3';
78 # e: pause 'mp3';
79 # r: stop 'mp3'; unload_music 'mp3'
80 if new_state == 'failed':
81 self.fail()
82 elif self.is_loaded(allow_substates=True) and\
83 new_state in ['initial', 'loading']:
84 self.reload(reloading=True)
85 elif self.is_loading() and new_state.startswith('loaded_'):
86 self.success()
87
88 # Machine states / events
89 def on_enter_loading(self, reloading=False):
90 if reloading:
91 return
92 if hasattr(actions, self.action):
93 if 'music' in self.arguments and\
94 self.action not in ['unload_music', 'load_music']:
95 self.arguments['music'].subscribe_state_change(
96 self.callback_music_state)
97 else:
98 self.success()
99 else:
100 error_print("Unknown action {}".format(self.action))
101 self.fail()
102
103 def on_enter_loaded_running(self, key_start_time):
104 debug_print(self.description())
105 if hasattr(actions, self.action):
106 getattr(actions, self.action).run(self,
107 key_start_time=key_start_time, **self.arguments)
108
109 def on_enter_destroyed(self):
110 if 'music' in self.arguments:
111 self.arguments['music'].unsubscribe_state_change(
112 self.callback_music_state)
113
114 def notify_state_change(self, *args, **kwargs):
115 self.key.callback_action_state_changed()
116
117 # This one cannot be in the Machine state since it would be queued to run
118 # *after* the wait is ended...
119 def interrupt(self):
120 if getattr(actions, self.action, None) and\
121 hasattr(getattr(actions, self.action), 'interrupt'):
122 return getattr(getattr(actions, self.action), 'interrupt')(
123 self, **self.arguments)
124
125 def pause(self):
126 if getattr(actions, self.action, None) and\
127 hasattr(getattr(actions, self.action), 'pause'):
128 return getattr(getattr(actions, self.action), 'pause')(
129 self, **self.arguments)
130
131 def unpause(self):
132 if getattr(actions, self.action, None) and\
133 hasattr(getattr(actions, self.action), 'unpause'):
134 return getattr(getattr(actions, self.action), 'unpause')(
135 self, **self.arguments)
136
137 def reset(self):
138 if getattr(actions, self.action, None) and\
139 hasattr(getattr(actions, self.action), 'reset'):
140 return getattr(getattr(actions, self.action), 'reset')(
141 self, **self.arguments)
142
143 # Helpers
144 def music_list(self, music):
145 if music is not None:
146 return [music]
147 else:
148 return self.mapping.open_files.values()
149
150 def description(self):
151 if hasattr(actions, self.action):
152 return getattr(actions, self.action)\
153 .description(self, **self.arguments)
154 else:
155 return _("unknown action {}").format(self.action)