view foodtruck/pubutil.py @ 602:c6bc0ef03f82

admin: Better UI for publishing websites. * Support multiple publish targets. * Dedicated UI for publishing. * Some UI polish.
author Ludovic Chabant <ludovic@chabant.com>
date Wed, 27 Jan 2016 18:02:25 -0800
parents
children 978d8bca9fb3
line wrap: on
line source

import os
import os.path
import time
import signal
import logging
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()


if app.config['FOODTRUCK_CMDLINE_MODE']:
    # Make sure CTRL+C works correctly.
    signal.signal(signal.SIGINT,
                  lambda *args: _shutdown_server_and_raise_sigint())


class PublishLogReader(object):
    _pub_max_time = 10 * 60   # Don't bother about pubs 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._pub_pid_mtime = 0
        self._last_seek = 0
        self._last_ping_time = 0

    def run(self):
        logger.debug("Opening publish 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._pub_pid_mtime
                try:
                    self._pub_pid_mtime = os.path.getmtime(self.pid_path)
                    if time.time() - self._pub_pid_mtime > \
                            self._pub_max_time:
                        self._pub_pid_mtime = 0
                except OSError:
                    self._pub_pid_mtime = 0

                # Send data.
                new_data = None
                if self._pub_pid_mtime > 0 or prev_mtime > 0:
                    if self._last_seek == 0:
                        outstr = 'event: message\ndata: Publish 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._pub_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 publish log...")