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 increment_state(self):
52 return UpdateStateMachine.INFINITE_PERIOD
55 return "NopStateStrategy()"
58 class ConstantStateStrategy(object):
60 def __init__(self, timeout):
61 assert 0 < timeout or timeout == UpdateStateMachine.INFINITE_PERIOD
62 self._timeout = timeout
64 def initialize_state(self):
67 def increment_state(self):
75 return "ConstantStateStrategy(timeout=%r)" % self._timeout
78 class GeometricStateStrategy(object):
80 def __init__(self, init, min, max):
81 assert 0 < init and init < max and init != UpdateStateMachine.INFINITE_PERIOD
82 assert 0 < min and min != UpdateStateMachine.INFINITE_PERIOD
83 assert min < max or max == UpdateStateMachine.INFINITE_PERIOD
89 def initialize_state(self):
90 self._current = self._min
92 def increment_state(self):
93 if self._max == UpdateStateMachine.INFINITE_PERIOD:
96 self._current = min(2 * self._current, self._max - self._init)
100 timeout = self._init + self._current
104 return "GeometricStateStrategy(init=%r, min=%r, max=%r)" % (
105 self._init, self._min, self._max
109 class StateMachine(object):
111 STATE_ACTIVE = 0, "active"
112 STATE_IDLE = 1, "idle"
116 raise NotImplementedError("Abstract")
119 raise NotImplementedError("Abstract")
122 raise NotImplementedError("Abstract")
124 def set_state(self, state):
125 raise NotImplementedError("Abstract")
129 raise NotImplementedError("Abstract")
132 class MasterStateMachine(StateMachine):
136 self._state = self.STATE_ACTIVE
138 def append_machine(self, machine):
139 self._machines.append(machine)
142 # Confirm we are all on the same page
143 for machine in self._machines:
144 machine.set_state(self._state)
145 for machine in self._machines:
149 for machine in self._machines:
153 for machine in self._machines:
156 def set_state(self, state):
158 for machine in self._machines:
159 machine.set_state(state)
166 class UpdateStateMachine(StateMachine):
167 # Making sure the it is initialized is finicky, be careful
170 DEFAULT_MAX_TIMEOUT = to_seconds(hours=24)
174 def __init__(self, updateItems, name="", maxTime = DEFAULT_MAX_TIMEOUT):
176 self._updateItems = updateItems
177 self._maxTime = maxTime
179 self._state = self.STATE_ACTIVE
180 self._timeoutId = None
182 self._strategies = {}
183 self._callback = coroutines.func_sink(
184 coroutines.expand_positional(
185 self._request_reset_timers
190 return """UpdateStateMachine(
193 )""" % (self._name, self._strategies)
195 def set_state_strategy(self, state, strategy):
196 self._strategies[state] = strategy
199 assert self._timeoutId is None
200 for strategy in self._strategies.itervalues():
201 strategy.initialize_state()
202 if self._strategy.timeout != self.INFINITE_PERIOD:
203 self._timeoutId = gobject.idle_add(self._on_timeout)
204 _moduleLogger.info("%s Starting State Machine" % (self._name, ))
207 _moduleLogger.info("%s Stopping State Machine" % (self._name, ))
211 assert self._timeoutId is None
212 self._callback = None
214 def set_state(self, newState):
215 if self._state == newState:
217 oldState = self._state
218 _moduleLogger.info("%s Transitioning from %s to %s" % (self._name, oldState, newState))
220 self._state = newState
227 def reset_timers(self):
228 _moduleLogger.info("%s Resetting State Machine" % (self._name, ))
232 def request_reset_timers(self):
233 return self._callback
237 return self._strategies[self._state]
239 @gtk_toolbox.log_exception(_moduleLogger)
240 def _request_reset_timers(self, *args):
243 def _reset_timers(self):
244 if self._timeoutId is None:
245 return # not started yet
247 self._strategy.initialize_state()
248 self._schedule_update()
250 def _stop_update(self):
251 if self._timeoutId is None:
253 gobject.source_remove(self._timeoutId)
254 self._timeoutId = None
256 def _schedule_update(self):
257 assert self._timeoutId is None
258 self._strategy.increment_state()
259 nextTimeout = self._strategy.timeout
260 if nextTimeout != self.INFINITE_PERIOD and nextTimeout < self._maxTime:
261 self._timeoutId = gobject_utils.timeout_add_seconds(nextTimeout, self._on_timeout)
262 _moduleLogger.info("%s Next update in %s seconds" % (self._name, nextTimeout, ))
264 @gtk_toolbox.log_exception(_moduleLogger)
265 def _on_timeout(self):
266 self._timeoutId = None
267 self._schedule_update()
268 for item in self._updateItems:
270 item.update(force=True)
272 _moduleLogger.exception("Update failed for %r" % item)
273 return False # do not continue