pvl/syslog/tail.py
changeset 31 3e6d0feb115c
child 46 0bdbbda4cdea
--- /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()
+