implement a redshift device
[~helmut/onoff.git] / onoff / process.py
1 import logging
2 import os
3 import signal
4
5 from gi.repository import GObject
6
7 from .common import ST_ACTIVE, ST_TRANSITION, OnoffDevice
8
9 logger = logging.getLogger("onoff.process")
10
11 class OnoffProcess(OnoffDevice):
12     def __init__(self, command, start_wait=0, termsig=signal.SIGTERM):
13         OnoffDevice.__init__(self)
14         self.command = command
15         self.start_wait = start_wait
16         self.termsig = termsig
17         self.state = 0
18         self.pid = None
19         self.starting = None # timeout event during start
20         self.watch = None # watch event
21
22     def start_process(self):
23         assert self.state == 0
24         assert self.pid is None
25         assert self.starting is None
26         logger.info("starting command %s", " ".join(self.command))
27         self.state = ST_ACTIVE|ST_TRANSITION
28         ret = GObject.spawn_async(self.command, flags=GObject.SPAWN_SEARCH_PATH | GObject.SPAWN_DO_NOT_REAP_CHILD)
29         self.pid = ret[0]
30         logger.debug("started as pid %d", self.pid)
31         self.watch = GObject.child_watch_add(self.pid, self.process_died)
32         self.starting = GObject.timeout_add(1000 * self.start_wait,
33                                             self.process_started)
34         self.changestate(self.state)
35
36     def process_died(self, pid, condition):
37         assert self.state != 0
38         assert self.pid == pid
39         assert self.watch is not None
40         self.state = 0
41         self.watch = None
42         self.pid = None
43         logger.info("process %d died", pid)
44         self.changestate(self.state)
45
46     def process_started(self):
47         assert self.state == ST_ACTIVE|ST_TRANSITION
48         assert self.starting is not None
49         self.state = ST_ACTIVE
50         self.starting = None
51         logger.debug("process started")
52         self.changestate(self.state)
53
54     def stop_process(self):
55         assert self.state & ST_ACTIVE
56         if self.state & ST_TRANSITION:
57             logger.debug("cancelling start notification")
58             assert self.starting is not None
59             ret = GObject.source_remove(self.starting)
60             assert ret
61             self.starting = None
62         self.state = ST_TRANSITION
63         logger.info("killing process %d", self.pid)
64         os.kill(self.pid, self.termsig)
65         self.changestate(self.state)
66
67     def activate(self):
68         if self.state == 0:
69             self.start_process()
70         else:
71             logger.debug("not activating from state %d", self.state)
72
73     def deactivate(self):
74         if self.state & ST_ACTIVE:
75             self.stop_process()
76         else:
77             logger.debug("not deactivating from state %d", self.state)
78
79     def close(self):
80         self.deactivate()