ThrottledDevice: speed up quick reactivation
[~helmut/onoff.git] / onoff / command.py
1 import logging
2
3 from .common import OnoffDevice, ST_ACTIVE, ST_TRANSITION
4 from .gobject import spawn_child
5
6 logger = logging.getLogger("onoff.command")
7
8 class OnoffCommand(OnoffDevice):
9     """A device that is enabled and disabled by executing separate commands.
10     The transition period is the duration of the commands.
11     @type pid: int or None
12     @ivar pid: is either None or the pid of a transition command as long as
13             it lives.
14     @ivar watch: is either None or a GObject event source id of the
15             callback waiting for the termination of the spawned process.
16     @type desired_state: bool
17     @ivar desired_state: is the state that should be transitioned to
18     @type target_state: bool
19     @ivar target_state: is the state that we are currently transitioning to
20     """
21     def __init__(self, oncommand, offcommand):
22         """
23
24         @type oncommand: [str]
25         @param oncommand: an argument vector to be executed for activation.
26         @type offcommand: [str]
27         @param offcommand: an argument vector to be executed for deactivation.
28         @note: For both commands the first element is used as executable and
29                 looked up in $PATH.
30         """
31         OnoffDevice.__init__(self)
32         self.oncommand = oncommand
33         self.offcommand = offcommand
34         self.desired_state = 0 # bit mask of just ST_ACTIVE
35         self.pid = None
36         self.watch = None
37         self.target_state = 0 # state the last command targeted
38
39     @property
40     def state(self):
41         if self.pid is not None:
42             return self.desired_state | ST_TRANSITION
43         return self.desired_state
44
45     def transition(self, state):
46         command, name = [(self.offcommand, "offcommand"),
47                          (self.oncommand, "oncommand")][state]
48         self.target_state = state
49         logger.info("invoking %s %s", name, " ".join(command))
50         self.pid, self.watch = spawn_child(command, self.process_died)
51         logger.debug("started %s as pid %d", name, self.pid)
52         self.changestate(self.state)
53
54     def process_died(self, pid, condition):
55         assert self.pid == pid
56         assert self.watch is not None
57         self.watch = None
58         self.pid = None
59         logger.info("command %d targeting %d completed", pid, self.target_state)
60         if self.desired_state == self.target_state:
61             self.changestate(self.state)
62         else:
63             logger.info("desired state changed to %d during invocation " +
64                         "targeting %d", self.desired_state, self.target_state)
65             self.transition(self.desired_state)
66
67     def activate(self):
68         if self.desired_state != ST_ACTIVE:
69             self.desired_state = ST_ACTIVE
70             if self.pid is None:
71                 self.transition(self.desired_state)
72
73     def deactivate(self):
74         if self.desired_state != 0:
75             self.desired_state = 0
76             if self.pid is None:
77                 self.transition(self.desired_state)