use the enum module to represent states
[~helmut/onoff.git] / onoff / process.py
1 import logging
2 import os
3 import signal
4
5 from .common import OnoffDevice, OnoffState
6 from .gobject import spawn_child
7
8 logger = logging.getLogger("onoff.process")
9
10 class OnoffProcess(OnoffDevice):
11     """A device that in activated by starting a process and deactivated by
12     killing the process.
13
14     @type pid: int or None
15     @ivar pid: is either None if there is no process or the pid of the
16             spawned process
17     @ivar starting: is either None or a ScheduledFunction representing the
18             callback signalling the end of the activation transition.
19     @type killed: bool
20     @ivar killed: indicates whether the termination signal has been sent
21             to the spawned process.
22     """
23     def __init__(self, command, termsig=signal.SIGTERM):
24         """
25         @type command: [str]
26         @param command: an argument vector to be executed. The first element
27                 is used as executable and looked up in $PATH.
28         @param termsig: termination signal to be sent to the process to
29                 deactivate it. The process must exit in response to this
30                 signal.
31         """
32         OnoffDevice.__init__(self)
33         self.command = command
34         self.termsig = termsig
35         self.desired_state = OnoffState.inactive
36         self.pid = None
37         self.killed = False
38
39     @property
40     def state(self):
41         if self.killed:
42             return self.desired_state | OnoffState.transition
43         return self.desired_state
44
45     def start_process(self):
46         assert self.pid is None
47         logger.info("starting command %s", " ".join(self.command))
48         self.pid = spawn_child(self.command, self.process_died)
49         logger.debug("started as pid %d", self.pid)
50         assert self.desired_state == OnoffState.active
51         logger.debug("process started")
52         self.changestate(self.state)
53
54     def process_died(self, pid, condition):
55         assert self.pid == pid
56         self.pid = None
57         self.killed = False
58         logger.info("process %d died", pid)
59         if self.desired_state == OnoffState.active:
60             self.start_process()
61         else:
62             self.changestate(self.state)
63
64     def activate(self):
65         if self.desired_state != OnoffState.active:
66             self.desired_state = OnoffState.active
67             if self.pid is None:
68                 self.start_process()
69             else:
70                 logger.debug("already activated. nothing to do")
71
72     def deactivate(self):
73         if self.desired_state != OnoffState.inactive:
74             self.desired_state = OnoffState.inactive
75             if self.pid is not None and not self.killed:
76                 logger.info("killing process %d", self.pid)
77                 os.kill(self.pid, self.termsig)
78                 self.killed = True
79                 self.changestate(self.state)
80             else:
81                 logger.debug("already deactivated. nothing to do.")
82
83     def close(self):
84         self.deactivate()