7 import util.go_utils as gobject_utils
8 import util.coroutines as coroutines
12 _moduleLogger = logging.getLogger("gvoice.state_machine")
15 def to_milliseconds(**kwd):
16 if "milliseconds" in kwd:
17 return kwd["milliseconds"]
18 elif "seconds" in kwd:
19 return kwd["seconds"] * 1000
20 elif "minutes" in kwd:
21 return kwd["minutes"] * 1000 * 60
23 return kwd["hours"] * 1000 * 60 * 60
24 raise KeyError("Unknown arg: %r" % kwd)
27 def to_seconds(**kwd):
28 if "milliseconds" in kwd:
29 return kwd["milliseconds"] / 1000
30 elif "seconds" in kwd:
32 elif "minutes" in kwd:
33 return kwd["minutes"] * 60
35 return kwd["hours"] * 60 * 60
36 raise KeyError("Unknown arg: %r" % kwd)
39 class NopStateStrategy(object):
44 def initialize_state(self):
47 def reinitialize_state(self):
50 def increment_state(self):
55 return UpdateStateMachine.INFINITE_PERIOD
58 return "NopStateStrategy()"
61 class ConstantStateStrategy(object):
63 def __init__(self, timeout):
64 assert 0 < timeout or timeout == UpdateStateMachine.INFINITE_PERIOD
65 self._timeout = timeout
67 def initialize_state(self):
70 def reinitialize_state(self):
73 def increment_state(self):
81 return "ConstantStateStrategy(timeout=%r)" % self._timeout
84 class NTimesStateStrategy(object):
86 def __init__(self, timeouts, postTimeout):
87 assert 0 < len(timeouts)
88 for timeout in timeouts:
89 assert 0 < timeout or timeout == UpdateStateMachine.INFINITE_PERIOD
90 assert 0 < postTimeout or postTimeout == UpdateStateMachine.INFINITE_PERIOD
91 self._timeouts = timeouts
92 self._postTimeout = postTimeout
94 self._attemptCount = 0
96 def initialize_state(self):
97 self._attemptCount = len(self._timeouts)
99 def reinitialize_state(self):
100 self._attemptCount = 0
102 def increment_state(self):
103 self._attemptCount += 1
108 return self._timeouts[self._attemptCount]
110 return self._postTimeout
113 return "NTimesStateStrategy(timeouts=%r, postTimeout=%r)" % (
119 class GeometricStateStrategy(object):
121 def __init__(self, init, min, max):
122 assert 0 < init and init < max or init == UpdateStateMachine.INFINITE_PERIOD
123 assert 0 < min or min == UpdateStateMachine.INFINITE_PERIOD
124 assert min < max or max == UpdateStateMachine.INFINITE_PERIOD
130 def initialize_state(self):
131 self._current = self._max
133 def reinitialize_state(self):
134 self._current = self._min
136 def increment_state(self):
137 if self._current == UpdateStateMachine.INFINITE_PERIOD:
139 if self._init == UpdateStateMachine.INFINITE_PERIOD:
140 self._current = UpdateStateMachine.INFINITE_PERIOD
141 elif self._max == UpdateStateMachine.INFINITE_PERIOD:
144 self._current = min(2 * self._current, self._max - self._init)
148 if UpdateStateMachine.INFINITE_PERIOD in (self._init, self._current):
149 timeout = UpdateStateMachine.INFINITE_PERIOD
151 timeout = self._init + self._current
155 return "GeometricStateStrategy(init=%r, min=%r, max=%r)" % (
156 self._init, self._min, self._max
160 class StateMachine(object):
162 STATE_ACTIVE = 0, "active"
163 STATE_IDLE = 1, "idle"
167 raise NotImplementedError("Abstract")
170 raise NotImplementedError("Abstract")
173 raise NotImplementedError("Abstract")
175 def set_state(self, state):
176 raise NotImplementedError("Abstract")
180 raise NotImplementedError("Abstract")
183 class MasterStateMachine(StateMachine):
187 self._state = self.STATE_ACTIVE
189 def append_machine(self, machine):
190 self._machines.append(machine)
193 # Confirm we are all on the same page
194 for machine in self._machines:
195 machine.set_state(self._state)
196 for machine in self._machines:
200 for machine in self._machines:
204 for machine in self._machines:
207 def set_state(self, state):
209 for machine in self._machines:
210 machine.set_state(state)
217 class UpdateStateMachine(StateMachine):
218 # Making sure the it is initialized is finicky, be careful
221 DEFAULT_MAX_TIMEOUT = to_seconds(hours=24)
225 def __init__(self, updateItems, name="", maxTime = DEFAULT_MAX_TIMEOUT):
227 self._updateItems = updateItems
228 self._maxTime = maxTime
230 self._state = self.STATE_ACTIVE
231 self._timeoutId = None
233 self._strategies = {}
234 self._callback = coroutines.func_sink(
235 coroutines.expand_positional(
236 self._request_reset_timers
241 return """UpdateStateMachine(
244 )""" % (self._name, self._strategies)
246 def set_state_strategy(self, state, strategy):
247 self._strategies[state] = strategy
250 assert self._timeoutId is None
251 for strategy in self._strategies.itervalues():
252 strategy.initialize_state()
253 if self._strategy.timeout != self.INFINITE_PERIOD:
254 self._timeoutId = gobject.idle_add(self._on_timeout)
255 _moduleLogger.info("%s Starting State Machine" % (self._name, ))
258 _moduleLogger.info("%s Stopping State Machine" % (self._name, ))
262 assert self._timeoutId is None
263 self._callback = None
265 def set_state(self, newState):
266 if self._state == newState:
268 oldState = self._state
269 _moduleLogger.info("%s Transitioning from %s to %s" % (self._name, oldState, newState))
271 self._state = newState
272 self._reset_timers(initialize=True)
278 def reset_timers(self):
282 def request_reset_timers(self):
283 return self._callback
287 return self._strategies[self._state]
293 @gtk_toolbox.log_exception(_moduleLogger)
294 def _request_reset_timers(self, *args):
297 def _reset_timers(self, initialize=False):
298 if self._timeoutId is None:
299 return # not started yet
300 _moduleLogger.info("%s Resetting State Machine" % (self._name, ))
303 self._strategy.initialize_state()
305 self._strategy.reinitialize_state()
306 self._schedule_update()
308 def _stop_update(self):
309 if self._timeoutId is None:
311 gobject.source_remove(self._timeoutId)
312 self._timeoutId = None
314 def _schedule_update(self):
315 assert self._timeoutId is None
316 self._strategy.increment_state()
317 nextTimeout = self._strategy.timeout
318 if nextTimeout != self.INFINITE_PERIOD and nextTimeout < self._maxTime:
319 assert 0 < nextTimeout
320 self._timeoutId = gobject_utils.timeout_add_seconds(nextTimeout, self._on_timeout)
321 _moduleLogger.info("%s Next update in %s seconds" % (self._name, nextTimeout, ))
323 _moduleLogger.info("%s No further updates (timeout is %s seconds)" % (self._name, nextTimeout, ))
325 @gtk_toolbox.log_exception(_moduleLogger)
326 def _on_timeout(self):
327 self._timeoutId = None
328 self._schedule_update()
329 for item in self._updateItems:
331 item.update(force=True)
333 _moduleLogger.exception("Update failed for %r" % item)
334 return False # do not continue