9 import util.algorithms as algorithms
10 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
22 raise KeyError("Unknown arg: %r" % kwd)
25 class StateMachine(object):
27 STATE_ACTIVE = "active"
31 _ACTION_UPDATE = "update"
32 _ACTION_RESET = "reset"
35 _INITIAL_ACTIVE_PERIOD = int(_to_milliseconds(seconds=5))
36 _FINAL_ACTIVE_PERIOD = int(_to_milliseconds(minutes=2))
37 _IDLE_PERIOD = int(_to_milliseconds(minutes=10))
42 def __init__(self, initItems, updateItems):
43 self._initItems = initItems
44 self._updateItems = updateItems
46 self._actions = Queue.Queue()
47 self._state = self.STATE_ACTIVE
48 self._timeoutId = None
50 self._currentPeriod = self._INITIAL_ACTIVE_PERIOD
51 self._set_initial_period()
54 assert self._thread is None
55 self._thread = threading.Thread(target=self._run)
56 self._thread.setDaemon(self._IS_DAEMON)
60 if self._thread is not None:
61 self._actions.put(self._ACTION_STOP)
64 _moduleLogger.info("Stopping an already stopped state machine")
66 def set_state(self, state):
73 def reset_timers(self):
74 self._actions.put(self._ACTION_RESET)
77 def request_reset_timers(self, args):
81 for item in self._initItems:
85 _moduleLogger.exception("Initial update failed for %r" % item)
87 # empty the task queue
88 actions = list(algorithms.itr_available(self._actions, initiallyBlock = False))
89 self._schedule_update()
90 if len(self._updateItems) == 0:
94 # block till we get a task, or get all the tasks if we were late
95 actions = list(algorithms.itr_available(self._actions, initiallyBlock = True))
97 if self._ACTION_STOP in actions:
100 elif self._ACTION_RESET in actions:
102 elif self._ACTION_UPDATE in actions:
103 for item in self._updateItems:
105 item.update(force=True)
107 _moduleLogger.exception("Update failed for %r" % item)
108 self._schedule_update()
110 def _set_initial_period(self):
111 self._currentPeriod = self._INITIAL_ACTIVE_PERIOD / 2 # We will double it later
113 def _reset_timers(self):
115 self._set_initial_period()
116 self._schedule_update()
118 def _schedule_update(self):
119 nextTimeout = self._calculate_step(self._state, self._currentPeriod)
120 nextTimeout = int(nextTimeout)
121 if nextTimeout != self._INFINITE_PERIOD:
122 self._timeoutId = gobject.timeout_add(nextTimeout, self._on_timeout)
123 self._currentPeriod = nextTimeout
125 def _stop_update(self):
126 if self._timeoutId is None:
128 gobject.source_remove(self._timeoutId)
129 self._timeoutId = None
131 def _on_timeout(self):
132 self._actions.put(self._ACTION_UPDATE)
133 return False # do not continue
136 def _calculate_step(cls, state, period):
137 if state == cls.STATE_ACTIVE:
138 return min(period * 2, cls._FINAL_ACTIVE_PERIOD)
139 elif state == cls.STATE_IDLE:
140 return cls._IDLE_PERIOD
141 elif state == cls.STATE_DND:
142 return cls._INFINITE_PERIOD
144 raise RuntimeError("Unknown state: %r" % (state, ))