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
13 import util.algorithms as algorithms
14 import util.go_utils as gobject_utils
15 import util.coroutines as coroutines
19 _moduleLogger = logging.getLogger("gvoice.state_machine")
22 def _to_milliseconds(**kwd):
23 if "milliseconds" in kwd:
24 return kwd["milliseconds"]
25 elif "seconds" in kwd:
26 return kwd["seconds"] * 1000
27 elif "minutes" in kwd:
28 return kwd["minutes"] * 1000 * 60
29 raise KeyError("Unknown arg: %r" % kwd)
32 class StateMachine(object):
34 STATE_ACTIVE = "active"
38 _ACTION_UPDATE = "update"
39 _ACTION_RESET = "reset"
42 _INITIAL_ACTIVE_PERIOD = int(_to_milliseconds(seconds=5))
43 _FINAL_ACTIVE_PERIOD = int(_to_milliseconds(minutes=2))
44 _IDLE_PERIOD = int(_to_milliseconds(minutes=10))
49 def __init__(self, initItems, updateItems):
50 self._initItems = initItems
51 self._updateItems = updateItems
53 self._state = self.STATE_ACTIVE
54 self._timeoutId = None
55 self._currentPeriod = self._INITIAL_ACTIVE_PERIOD
56 self._set_initial_period()
58 self._callback = coroutines.func_sink(
59 coroutines.expand_positional(
60 self._request_reset_timers
65 @gtk_toolbox.log_exception(_moduleLogger)
67 _moduleLogger.info("Starting State Machine")
68 for item in self._initItems:
72 _moduleLogger.exception("Initial update failed for %r" % item)
73 self._schedule_update()
76 _moduleLogger.info("Stopping an already stopped state machine")
79 def set_state(self, state):
86 def reset_timers(self):
90 def request_reset_timers(self):
94 @gtk_toolbox.log_exception(_moduleLogger)
95 def _request_reset_timers(self, *args):
98 def _set_initial_period(self):
99 self._currentPeriod = self._INITIAL_ACTIVE_PERIOD / 2 # We will double it later
101 def _schedule_update(self):
102 nextTimeout = self._calculate_step(self._state, self._currentPeriod)
103 nextTimeout = int(nextTimeout)
104 if nextTimeout != self._INFINITE_PERIOD:
105 self._timeoutId = gobject.timeout_add(nextTimeout, self._on_timeout)
106 self._currentPeriod = nextTimeout
108 def _stop_update(self):
109 if self._timeoutId is None:
111 gobject.source_remove(self._timeoutId)
112 self._timeoutId = None
114 def _reset_timers(self):
116 self._set_initial_period()
117 self._schedule_update()
119 def _on_timeout(self):
120 _moduleLogger.info("Update")
121 for item in self._updateItems:
123 item.update(force=True)
125 _moduleLogger.exception("Update failed for %r" % item)
126 self._schedule_update()
127 return False # do not continue
130 def _calculate_step(cls, state, period):
131 if state == cls.STATE_ACTIVE:
132 return min(period * 2, cls._FINAL_ACTIVE_PERIOD)
133 elif state == cls.STATE_IDLE:
134 return cls._IDLE_PERIOD
135 elif state == cls.STATE_DND:
136 return cls._INFINITE_PERIOD
138 raise RuntimeError("Unknown state: %r" % (state, ))