Reducing debug output
[theonering] / src / gvoice / state_machine.py
1 #!/usr/bin/env python
2
3 """
4 @todo Look into switching from POLL_TIME = min(F * 2^n, MAX) to POLL_TIME = min(CONST + F * 2^n, MAX)
5 @todo Look into supporting more states that have a different F and MAX
6 """
7
8 import logging
9
10 import gobject
11
12 import util.go_utils as gobject_utils
13 import util.coroutines as coroutines
14 import gtk_toolbox
15
16
17 _moduleLogger = logging.getLogger("gvoice.state_machine")
18
19
20 def to_milliseconds(**kwd):
21         if "milliseconds" in kwd:
22                 return kwd["milliseconds"]
23         elif "seconds" in kwd:
24                 return kwd["seconds"] * 1000
25         elif "minutes" in kwd:
26                 return kwd["minutes"] * 1000 * 60
27         elif "hours" in kwd:
28                 return kwd["hours"] * 1000 * 60 * 60
29         raise KeyError("Unknown arg: %r" % kwd)
30
31
32 class NopStateStrategy(object):
33
34         def __init__(self):
35                 pass
36
37         def initialize_state(self):
38                 pass
39
40         def increment_state(self):
41                 pass
42
43         @property
44         def timeout(self):
45                 return UpdateStateMachine.INFINITE_PERIOD
46
47
48 class ConstantStateStrategy(object):
49
50         def __init__(self, timeout):
51                 assert 0 < timeout or timeout == UpdateStateMachine.INFINITE_PERIOD
52                 self._timeout = timeout
53
54         def initialize_state(self):
55                 pass
56
57         def increment_state(self):
58                 pass
59
60         @property
61         def timeout(self):
62                 return self._timeout
63
64
65 class GeometricStateStrategy(object):
66
67         def __init__(self, init, min, max):
68                 assert 0 < init and init < max and init != UpdateStateMachine.INFINITE_PERIOD
69                 assert 0 < min and min != UpdateStateMachine.INFINITE_PERIOD
70                 assert min < max or max == UpdateStateMachine.INFINITE_PERIOD
71                 self._min = min
72                 self._max = max
73                 self._init = init
74                 self._actualInit = init
75                 self._current = 0
76
77         def initialize_state(self):
78                 self._current = self._min / 2
79                 self._actualInit = self._init - self._min
80
81         def increment_state(self):
82                 if self._max == UpdateStateMachine.INFINITE_PERIOD:
83                         self._current *= 2
84                 else:
85                         self._current = min(2 * self._current, self._max - self._init)
86
87         @property
88         def timeout(self):
89                 return self._actualInit + self._current
90
91
92 class StateMachine(object):
93
94         STATE_ACTIVE = 0, "active"
95         STATE_IDLE = 1, "idle"
96         STATE_DND = 2, "dnd"
97
98         def start(self):
99                 raise NotImplementedError("Abstract")
100
101         def stop(self):
102                 raise NotImplementedError("Abstract")
103
104         def close(self):
105                 raise NotImplementedError("Abstract")
106
107         def set_state(self, state):
108                 raise NotImplementedError("Abstract")
109
110         @property
111         def state(self):
112                 raise NotImplementedError("Abstract")
113
114
115 class MasterStateMachine(StateMachine):
116
117         def __init__(self):
118                 self._machines = []
119                 self._state = self.STATE_ACTIVE
120
121         def append_machine(self, machine):
122                 self._machines.append(machine)
123
124         def start(self):
125                 # Confirm we are all on the same page
126                 for machine in self._machines:
127                         machine.set_state(self._state)
128                 for machine in self._machines:
129                         machine.start()
130
131         def stop(self):
132                 for machine in self._machines:
133                         machine.stop()
134
135         def close(self):
136                 for machine in self._machines:
137                         machine.close()
138
139         def set_state(self, state):
140                 self._state = state
141                 for machine in self._machines:
142                         machine.set_state(state)
143
144         @property
145         def state(self):
146                 return self._state
147
148
149 class UpdateStateMachine(StateMachine):
150         # Making sure the it is initialized is finicky, be careful
151
152         INFINITE_PERIOD = -1
153
154         _IS_DAEMON = True
155
156         def __init__(self, updateItems, name=""):
157                 self._name = name
158                 self._updateItems = updateItems
159
160                 self._state = self.STATE_ACTIVE
161                 self._timeoutId = None
162
163                 self._strategies = {}
164                 self._callback = coroutines.func_sink(
165                         coroutines.expand_positional(
166                                 self._request_reset_timers
167                         )
168                 )
169
170         def set_state_strategy(self, state, strategy):
171                 self._strategies[state] = strategy
172
173         def start(self):
174                 assert self._timeoutId is None
175                 for strategy in self._strategies.itervalues():
176                         strategy.initialize_state()
177                 self._timeoutId = gobject.idle_add(self._on_timeout)
178                 _moduleLogger.info("%s Starting State Machine" % (self._name, ))
179
180         def stop(self):
181                 self._stop_update()
182
183         def close(self):
184                 self._callback = None
185
186         def set_state(self, newState):
187                 if self._state == newState:
188                         return
189                 oldState = self._state
190                 _moduleLogger.info("%s Transitioning from %s to %s" % (self._name, oldState, newState))
191
192                 self._state = newState
193                 self._reset_timers()
194
195         @property
196         def state(self):
197                 return self._state
198
199         def reset_timers(self):
200                 self._reset_timers()
201
202         @property
203         def request_reset_timers(self):
204                 return self._callback
205
206         @property
207         def _strategy(self):
208                 return self._strategies[self._state]
209
210         @gobject_utils.async
211         @gtk_toolbox.log_exception(_moduleLogger)
212         def _request_reset_timers(self, *args):
213                 self._reset_timers()
214
215         def _set_initial_period(self):
216                 self._currentPeriod = self._INITIAL_ACTIVE_PERIOD / 2 # We will double it later
217
218         def _schedule_update(self):
219                 assert self._timeoutId is None
220                 self._strategy.increment_state()
221                 nextTimeout = self._strategy.timeout
222                 if nextTimeout != self.INFINITE_PERIOD:
223                         self._timeoutId = gobject.timeout_add(nextTimeout, self._on_timeout)
224                 _moduleLogger.info("%s Next update in %s ms" % (self._name, nextTimeout, ))
225
226         def _stop_update(self):
227                 if self._timeoutId is None:
228                         return
229                 gobject.source_remove(self._timeoutId)
230                 self._timeoutId = None
231
232         def _reset_timers(self):
233                 if self._timeoutId is None:
234                         return # not started yet
235                 self._stop_update()
236                 self._strategy.initialize_state()
237                 self._schedule_update()
238
239         @gtk_toolbox.log_exception(_moduleLogger)
240         def _on_timeout(self):
241                 _moduleLogger.debug("%s Update" % (self._name))
242                 for item in self._updateItems:
243                         try:
244                                 item.update(force=True)
245                         except Exception:
246                                 _moduleLogger.exception("Update failed for %r" % item)
247                 self._timeoutId = None
248                 self._schedule_update()
249                 return False # do not continue