--- /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()
+