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 GeometricStateStrategy(object):
86 def __init__(self, init, min, max):
87 assert 0 < init and init < max or init == UpdateStateMachine.INFINITE_PERIOD
88 assert 0 < min or min == UpdateStateMachine.INFINITE_PERIOD
89 assert min < max or max == UpdateStateMachine.INFINITE_PERIOD
95 def initialize_state(self):
96 self._current = self._max
98 def reinitialize_state(self):
99 self._current = self._min
101 def increment_state(self):
102 if self._current == UpdateStateMachine.INFINITE_PERIOD:
104 if self._init == UpdateStateMachine.INFINITE_PERIOD:
105 self._current = UpdateStateMachine.INFINITE_PERIOD
106 elif self._max == UpdateStateMachine.INFINITE_PERIOD:
109 self._current = min(2 * self._current, self._max - self._init)
113 if UpdateStateMachine.INFINITE_PERIOD in (self._init, self._current):
114 timeout = UpdateStateMachine.INFINITE_PERIOD
116 timeout = self._init + self._current
120 return "GeometricStateStrategy(init=%r, min=%r, max=%r)" % (
121 self._init, self._min, self._max
125 class StateMachine(object):
127 STATE_ACTIVE = 0, "active"
128 STATE_IDLE = 1, "idle"
132 raise NotImplementedError("Abstract")
135 raise NotImplementedError("Abstract")
138 raise NotImplementedError("Abstract")
140 def set_state(self, state):
141 raise NotImplementedError("Abstract")
145 raise NotImplementedError("Abstract")
148 class MasterStateMachine(StateMachine):
152 self._state = self.STATE_ACTIVE
154 def append_machine(self, machine):
155 self._machines.append(machine)
158 # Confirm we are all on the same page
159 for machine in self._machines:
160 machine.set_state(self._state)
161 for machine in self._machines:
165 for machine in self._machines:
169 for machine in self._machines:
172 def set_state(self, state):
174 for machine in self._machines:
175 machine.set_state(state)
182 class UpdateStateMachine(StateMachine):
183 # Making sure the it is initialized is finicky, be careful
186 DEFAULT_MAX_TIMEOUT = to_seconds(hours=24)
190 def __init__(self, updateItems, name="", maxTime = DEFAULT_MAX_TIMEOUT):
192 self._updateItems = updateItems
193 self._maxTime = maxTime
195 self._state = self.STATE_ACTIVE
196 self._timeoutId = None
198 self._strategies = {}
199 self._callback = coroutines.func_sink(
200 coroutines.expand_positional(
201 self._request_reset_timers
206 return """UpdateStateMachine(
209 )""" % (self._name, self._strategies)
211 def set_state_strategy(self, state, strategy):
212 self._strategies[state] = strategy
215 assert self._timeoutId is None
216 for strategy in self._strategies.itervalues():
217 strategy.initialize_state()
218 if self._strategy.timeout != self.INFINITE_PERIOD:
219 self._timeoutId = gobject.idle_add(self._on_timeout)
220 _moduleLogger.info("%s Starting State Machine" % (self._name, ))
223 _moduleLogger.info("%s Stopping State Machine" % (self._name, ))
227 assert self._timeoutId is None
228 self._callback = None
230 def set_state(self, newState):
231 if self._state == newState:
233 oldState = self._state
234 _moduleLogger.info("%s Transitioning from %s to %s" % (self._name, oldState, newState))
236 self._state = newState
243 def reset_timers(self):
247 def request_reset_timers(self):
248 return self._callback
252 return self._strategies[self._state]
254 @gtk_toolbox.log_exception(_moduleLogger)
255 def _request_reset_timers(self, *args):
258 def _reset_timers(self):
259 if self._timeoutId is None:
260 return # not started yet
261 _moduleLogger.info("%s Resetting State Machine" % (self._name, ))
263 self._strategy.reinitialize_state()
264 self._schedule_update()
266 def _stop_update(self):
267 if self._timeoutId is None:
269 gobject.source_remove(self._timeoutId)
270 self._timeoutId = None
272 def _schedule_update(self):
273 assert self._timeoutId is None
274 self._strategy.increment_state()
275 nextTimeout = self._strategy.timeout
276 if nextTimeout != self.INFINITE_PERIOD and nextTimeout < self._maxTime:
277 assert 0 < nextTimeout
278 self._timeoutId = gobject_utils.timeout_add_seconds(nextTimeout, self._on_timeout)
279 _moduleLogger.info("%s Next update in %s seconds" % (self._name, nextTimeout, ))
281 _moduleLogger.info("%s No further updates (timeout is %s seconds)" % (self._name, nextTimeout, ))
283 @gtk_toolbox.log_exception(_moduleLogger)
284 def _on_timeout(self):
285 self._timeoutId = None
286 self._schedule_update()
287 for item in self._updateItems:
289 item.update(force=True)
291 _moduleLogger.exception("Update failed for %r" % item)
292 return False # do not continue