Mercurial > piecrust2
diff foodtruck/views/baking.py @ 587:d4a01a023998
admin: Add "FoodTruck" admin panel from the side experiment project.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sat, 16 Jan 2016 14:24:35 -0800 |
parents | |
children | 79a31a3c947b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/foodtruck/views/baking.py Sat Jan 16 14:24:35 2016 -0800 @@ -0,0 +1,116 @@ +import os +import os.path +import time +import signal +import logging +from werkzeug.wrappers import Response +from flask import g, redirect +from flask.ext.login import login_required +from ..web import app + + +logger = logging.getLogger(__name__) + +server_shutdown = False + + +def _shutdown_server_and_raise_sigint(): + if not app.debug or os.environ.get('WERKZEUG_RUN_MAIN') == 'true': + # This is needed when hitting CTRL+C to shutdown the Werkzeug server, + # otherwise SSE generators will keep it alive. + logger.debug("Shutting down SSE generators...") + global server_shutdown + server_shutdown = True + raise KeyboardInterrupt() + + +# Make sure CTRL+C works correctly. +signal.signal(signal.SIGINT, + lambda *args: _shutdown_server_and_raise_sigint()) + + +class _BakeLogReader(object): + _bake_max_time = 10 * 60 # Don't bother about bakes older than 10mins. + _poll_interval = 2 # Check the PID file every 2 seconds. + _ping_interval = 30 # Send a ping message every 30 seconds. + + def __init__(self, pid_path, log_path): + self.pid_path = pid_path + self.log_path = log_path + self._bake_pid_mtime = 0 + self._last_seek = 0 + self._last_ping_time = 0 + + def run(self): + logger.debug("Opening bake log...") + + try: + while not server_shutdown: + # PING! + interval = time.time() - self._last_ping_time + if interval > self._ping_interval: + logger.debug("Sending ping...") + self._last_ping_time = time.time() + yield bytes("event: ping\ndata: 1\n\n", 'utf8') + + # Check pid file. + prev_mtime = self._bake_pid_mtime + try: + self._bake_pid_mtime = os.path.getmtime(self.pid_path) + if time.time() - self._bake_pid_mtime > \ + self._bake_max_time: + self._bake_pid_mtime = 0 + except OSError: + self._bake_pid_mtime = 0 + + # Send data. + new_data = None + if self._bake_pid_mtime > 0 or prev_mtime > 0: + if self._last_seek == 0: + outstr = 'event: message\ndata: Bake started.\n\n' + yield bytes(outstr, 'utf8') + + try: + with open(self.log_path, 'r', encoding='utf8') as fp: + fp.seek(self._last_seek) + new_data = fp.read() + self._last_seek = fp.tell() + except OSError: + pass + if self._bake_pid_mtime == 0: + self._last_seek = 0 + + if new_data: + logger.debug("SSE: %s" % outstr) + for line in new_data.split('\n'): + outstr = 'event: message\ndata: %s\n\n' % line + yield bytes(outstr, 'utf8') + + time.sleep(self._poll_interval) + + except GeneratorExit: + pass + + logger.debug("Closing bake log...") + + +@app.route('/bake', methods=['POST']) +@login_required +def bake_site(): + site = g.sites.get() + site.bake() + return redirect('/') + + +@app.route('/bakelog') +@login_required +def stream_bake_log(): + site = g.sites.get() + pid_path = os.path.join(site.root_dir, 'foodtruck_bake.pid') + log_path = os.path.join(site.root_dir, 'foodtruck_bake.log') + rdr = _BakeLogReader(pid_path, log_path) + + response = Response(rdr.run(), mimetype='text/event-stream') + response.headers['Cache-Control'] = 'no-cache' + return response +