comparison foodtruck/pubutil.py @ 613:e2e955a3bb25

publish: Add publish command. * Add `shell` publisher. * Refactor admin panel's publishing backend to use that, along with the new PID file support.
author Ludovic Chabant <ludovic@chabant.com>
date Thu, 04 Feb 2016 08:05:03 -0800
parents 978d8bca9fb3
children cbb170d9c894
comparison
equal deleted inserted replaced
612:2edaefcb82cd 613:e2e955a3bb25
1 import os 1 import os
2 import os.path 2 import os.path
3 import time 3 import time
4 import errno
4 import signal 5 import signal
5 import logging 6 import logging
6 from .web import app 7 from .web import app
7 8
8 9
25 # Make sure CTRL+C works correctly. 26 # Make sure CTRL+C works correctly.
26 signal.signal(signal.SIGINT, 27 signal.signal(signal.SIGINT,
27 lambda *args: _shutdown_server_and_raise_sigint()) 28 lambda *args: _shutdown_server_and_raise_sigint())
28 29
29 30
31 def _pid_exists(pid):
32 try:
33 os.kill(pid, 0)
34 except OSError as ex:
35 if ex.errno == errno.ESRCH:
36 # No such process.
37 return False
38 elif ex.errno == errno.EPERM:
39 # No permission, so process exists.
40 return True
41 else:
42 raise
43 else:
44 return True
45
46
47 def _read_pid_file(pid_file):
48 logger.debug("Reading PID file: %s" % pid_file)
49 try:
50 with open(pid_file, 'r') as fp:
51 pid_str = fp.read()
52
53 return int(pid_str.strip())
54 except Exception:
55 logger.error("Error reading PID file.")
56 raise
57
58
30 class PublishLogReader(object): 59 class PublishLogReader(object):
31 _pub_max_time = 10 * 60 # Don't bother about pubs older than 10mins. 60 _pub_max_time = 10 * 60 # Don't bother about pubs older than 10mins.
32 _poll_interval = 1 # Check the PID file every 1 seconds. 61 _poll_interval = 1 # Check the process every 1 seconds.
33 _ping_interval = 30 # Send a ping message every 30 seconds. 62 _ping_interval = 30 # Send a ping message every 30 seconds.
34 63
35 def __init__(self, pid_path, log_path): 64 def __init__(self, pid_path, log_path):
36 self.pid_path = pid_path 65 self.pid_path = pid_path
37 self.log_path = log_path 66 self.log_path = log_path
39 self._last_seek = -1 68 self._last_seek = -1
40 self._last_ping_time = 0 69 self._last_ping_time = 0
41 70
42 def run(self): 71 def run(self):
43 logger.debug("Opening publish log...") 72 logger.debug("Opening publish log...")
44 73 pid = None
74 is_running = False
45 try: 75 try:
46 while not server_shutdown: 76 while not server_shutdown:
47 # PING! 77 # PING!
48 interval = time.time() - self._last_ping_time 78 interval = time.time() - self._last_ping_time
49 if interval > self._ping_interval: 79 if interval > self._ping_interval:
50 logger.debug("Sending ping...") 80 logger.debug("Sending ping...")
51 self._last_ping_time = time.time() 81 self._last_ping_time = time.time()
52 yield bytes("event: ping\ndata: 1\n\n", 'utf8') 82 yield bytes("event: ping\ndata: 1\n\n", 'utf8')
53 83
54 # Check pid file. 84 # Check if the PID file has changed.
85 try:
86 new_mtime = os.path.getmtime(self.pid_path)
87 except OSError:
88 new_mtime = 0
89
90 if (new_mtime > 0 and
91 time.time() - new_mtime > self._pub_max_time):
92 new_mtime = 0
93
94 # Re-read the PID file.
55 prev_mtime = self._pub_pid_mtime 95 prev_mtime = self._pub_pid_mtime
56 try: 96 if new_mtime > 0 and new_mtime != prev_mtime:
57 self._pub_pid_mtime = os.path.getmtime(self.pid_path) 97 self._pub_pid_mtime = new_mtime
58 if time.time() - self._pub_pid_mtime > \ 98 pid = _read_pid_file(self.pid_path)
59 self._pub_max_time: 99 if pid:
60 self._pub_pid_mtime = 0 100 logger.debug("Monitoring new process, PID: %d" % pid)
61 except OSError: 101
62 self._pub_pid_mtime = 0 102 was_running = is_running
103 if pid:
104 is_running = _pid_exists(pid)
105 logger.debug(
106 "Process %d is %s" %
107 (pid, 'running' if is_running else 'not running'))
108 if not is_running:
109 pid = None
110 else:
111 is_running = False
63 112
64 # Send data. 113 # Send data.
65 new_data = None 114 new_data = None
66 if self._pub_pid_mtime > 0 or prev_mtime > 0: 115 if is_running or was_running:
67 if self._last_seek < 0: 116 if self._last_seek < 0:
68 outstr = 'event: message\ndata: Publish started.\n\n' 117 outstr = 'event: message\ndata: Publish started.\n\n'
69 yield bytes(outstr, 'utf8') 118 yield bytes(outstr, 'utf8')
70 self._last_seek = 0 119 self._last_seek = 0
71 120
74 fp.seek(self._last_seek) 123 fp.seek(self._last_seek)
75 new_data = fp.read() 124 new_data = fp.read()
76 self._last_seek = fp.tell() 125 self._last_seek = fp.tell()
77 except OSError: 126 except OSError:
78 pass 127 pass
79 if self._pub_pid_mtime == 0: 128 if not is_running:
80 self._last_seek = 0 129 self._last_seek = 0
81 130
82 if new_data: 131 if new_data:
83 logger.debug("SSE: %s" % outstr) 132 logger.debug("SSE: %s" % new_data)
84 for line in new_data.split('\n'): 133 for line in new_data.split('\n'):
85 outstr = 'event: message\ndata: %s\n\n' % line 134 outstr = 'event: message\ndata: %s\n\n' % line
86 yield bytes(outstr, 'utf8') 135 yield bytes(outstr, 'utf8')
87 136
88 time.sleep(self._poll_interval) 137 time.sleep(self._poll_interval)