diff -r 841d856293a1 -r 3e6d0feb115c pvl/syslog/tail.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pvl/syslog/tail.py Wed Oct 24 21:02:33 2012 +0300 @@ -0,0 +1,154 @@ +""" + `tail -f` style continuous file reads. + + Checks if the file was renamed on EOF, and reopens if so. +""" + +import os + +import logging; log = logging.getLogger('pvl.collectd.tail') + +class TailFile (object) : + """ + A file on the filesystem, that is appended to. + """ + + def __init__ (self, path) : + self.path = path + self._file = None + self._id = None + + self._open() + + @property + def file (self) : + """ + The underlying file objct, if opened. + """ + + if self._file is None : + raise ValueError("I/O operation on closed file: {0}".format(self.path)) + + return self._file + + @property + def fd (self) : + return self.file.fileno() + + def fileno (self) : + """ + The underlying OS fd. + """ + + return self.fd + + def _stat (self) : + """ + Return a key identifying the file at our path. + """ + + st = os.stat(self.path) + + return st.st_dev, st.st_ino + + def _open (self) : + assert self._file is None + + self._file = open(self.path, 'r') + self._id = self._stat() + + def _close (self) : + assert self._file is not None + + self._file.close() + self._file = None + + def _reopen (self) : + """ + Re-open, in case the file changed.. + """ + + self._close() + self._open() + + def _changed (self) : + """ + Has the underlying file changed? + """ + + return self._stat() != self._id + + def poll (self, timeout) : + """ + XXX: not really implemented... + """ + + import time + + time.sleep(timeout) + + def readline (self) : + """ + Reads a line from the file. + + Raises EOF on end-of-file. + """ + + line = self.file.readline() + + # eof? + if not line : + raise EOFError() + + return line + + 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: file changed: reopen") + self._reopen() + + if eof_mark : + # special token + yield None + + else : + log.debug("EOF: wait") + # done reading + return + + else : + yield line.strip() + + __iter__ = readlines + + def close (self) : + """ + Close the fifo. + """ + + if self._file is None : + raise ValueError("File already closed: {0}".format(self.path)) + + self._close() + + def __del__ (self) : + """ + Cleanup + """ + + if self._file is not None : + self._close() +