pvl/syslog/tail.py
author Tero Marttila <terom@fixme.fi>
Fri, 04 Jan 2013 18:43:18 +0200
changeset 49 30c615bf7751
parent 46 0bdbbda4cdea
child 53 685fd90bc610
permissions -rw-r--r--
pvl.syslog.tail: split Tail vs TailFile
"""
    Iterate over input lines in files. 
    
    Can be read up to eof, on blocking inputs, or polled with a timeout (see: pvl.syslog.syslog.SyslogSource)
"""

import os

import logging; log = logging.getLogger('pvl.syslog.tail')

class Tail (object) :
    """
        Follow a file-like object, reading lines until EOF.

        Works with python file objects that buffer readlines() when using e.g. `tail -f ... | python -u ...`.
    """

    def __init__ (self, file) :
        self.file = file

    def readline (self) :
        """
            Reads a line from the file, without trailing \n

            Raises EOF on end-of-file.
        """

        line = self.file.readline()

        if not line :
            raise EOFError()
        else :
            return line.rstrip('\n')

    def readlines (self) :
        """
            Reads any available lines from the file.
        """

        while True :
            try :
                line = self.readline()

            except EOFError :
                log.debug("EOF")
                break

            else :
                yield line

    def skip (self) :
        """
            Skip any available lines.
        """

        for line in self.readlines() :
            pass

    __iter__ = readlines

class TailFile (Tail) :
    """
        Follow a file on the filesystem, reading lines until EOF, and re-opening if replaced.
    """

    def __init__ (self, path) :
        self.path = path
        self.reopen()
    
    def stat (cls) :
        """
            Return a key identifying the file at our path.
        """

        st = os.stat(self.path)

        return st.st_dev, st.st_ino
    
    def open (cls) :
        return open(self.path, 'r')

    def reopen (cls) :
        self.file = self.open()
        self._stat = self.stat()

    def changed (self) :
        """
            Has the underlying file changed?
        """

        return self.stat() != self._stat

    def readlines (self, eof_mark=False) :
        """
            Reads any available lines from the file.

            Reopens the file on EOF if the underlying file changed.

                eof_mark:   yields a special None line when this happens.
        """

        while True :
            try :
                line = self.readline()

            except EOFError :
                if self.changed() :
                    log.debug("EOF: reopen")
                    self.reopen()
                    
                    if eof_mark :
                        yield None # special token
                    
                    # keep going
                    continue

                else :
                    log.debug("EOF")
                    break # wait

            else :
                yield line

    __iter__ = readlines