terom@31: """ terom@49: Iterate over input lines in files. terom@49: terom@49: Can be read up to eof, on blocking inputs, or polled with a timeout (see: pvl.syslog.syslog.SyslogSource) terom@31: """ terom@31: terom@31: import os terom@31: terom@49: import logging; log = logging.getLogger('pvl.syslog.tail') terom@31: terom@49: class Tail (object) : terom@31: """ terom@49: Follow a file-like object, reading lines until EOF. terom@49: terom@49: Works with python file objects that buffer readlines() when using e.g. `tail -f ... | python -u ...`. terom@31: """ terom@31: terom@49: def __init__ (self, file) : terom@49: self.file = file terom@46: terom@49: def readline (self) : terom@31: """ terom@49: Reads a line from the file, without trailing \n terom@49: terom@49: Raises EOF on end-of-file. terom@31: """ terom@31: terom@49: line = self.file.readline() terom@31: terom@49: if not line : terom@49: raise EOFError() terom@49: else : terom@49: return line.rstrip('\n') terom@31: terom@49: def readlines (self) : terom@31: """ terom@49: Reads any available lines from the file. terom@31: """ terom@31: terom@49: while True : terom@49: try : terom@49: line = self.readline() terom@31: terom@49: except EOFError : terom@49: log.debug("EOF") terom@49: break terom@49: terom@49: else : terom@49: yield line terom@49: terom@49: def skip (self) : terom@49: """ terom@49: Skip any available lines. terom@49: """ terom@49: terom@49: for line in self.readlines() : terom@49: pass terom@49: terom@49: __iter__ = readlines terom@49: terom@49: class TailFile (Tail) : terom@49: """ terom@49: Follow a file on the filesystem, reading lines until EOF, and re-opening if replaced. terom@49: """ terom@49: terom@49: def __init__ (self, path) : terom@49: self.path = path terom@49: self.reopen() terom@49: terom@49: def stat (cls) : terom@31: """ terom@31: Return a key identifying the file at our path. terom@31: """ terom@31: terom@31: st = os.stat(self.path) terom@31: terom@31: return st.st_dev, st.st_ino terom@49: terom@49: def open (cls) : terom@49: return open(self.path, 'r') terom@31: terom@49: def reopen (cls) : terom@49: self.file = self.open() terom@49: self._stat = self.stat() terom@31: terom@49: def changed (self) : terom@31: """ terom@31: Has the underlying file changed? terom@31: """ terom@31: terom@49: return self.stat() != self._stat terom@31: terom@31: def readlines (self, eof_mark=False) : terom@31: """ terom@31: Reads any available lines from the file. terom@31: terom@31: Reopens the file on EOF if the underlying file changed. terom@31: terom@31: eof_mark: yields a special None line when this happens. terom@31: """ terom@31: terom@31: while True : terom@31: try : terom@31: line = self.readline() terom@31: terom@31: except EOFError : terom@49: if self.changed() : terom@49: log.debug("EOF: reopen") terom@49: self.reopen() terom@31: terom@31: if eof_mark : terom@49: yield None # special token terom@49: terom@49: # keep going terom@49: continue terom@31: terom@31: else : terom@49: log.debug("EOF") terom@49: break # wait terom@31: terom@31: else : terom@49: yield line terom@31: terom@31: __iter__ = readlines terom@31: