pvl/syslog/tail.py
changeset 49 30c615bf7751
parent 46 0bdbbda4cdea
child 53 685fd90bc610
--- a/pvl/syslog/tail.py	Fri Jan 04 14:19:05 2013 +0200
+++ b/pvl/syslog/tail.py	Fri Jan 04 18:43:18 2013 +0200
@@ -1,52 +1,73 @@
 """
-    `tail -f` style continuous file reads.
-
-    Checks if the file was renamed on EOF, and reopens if so.
+    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.collectd.tail')
+import logging; log = logging.getLogger('pvl.syslog.tail')
 
-class TailFile (object) :
+class Tail (object) :
     """
-        A file on the filesystem, that is appended to.
+        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, path, skip=None) :
-        self.path = path
-        self._file = None
-        self._id = None
-
-        self._open()
+    def __init__ (self, file) :
+        self.file = file
 
-        if skip :
-            for line in self :
-                pass
-    
-    @property
-    def file (self) :
+    def readline (self) :
         """
-            The underlying file objct, if opened.
+            Reads a line from the file, without trailing \n
+
+            Raises EOF on end-of-file.
         """
 
-        if self._file is None :
-            raise ValueError("I/O operation on closed file: {0}".format(self.path))
-
-        return self._file
+        line = self.file.readline()
 
-    @property
-    def fd (self) :
-        return self.file.fileno()
+        if not line :
+            raise EOFError()
+        else :
+            return line.rstrip('\n')
 
-    def fileno (self) :
+    def readlines (self) :
         """
-            The underlying OS fd.
+            Reads any available lines from the file.
         """
 
-        return self.fd
+        while True :
+            try :
+                line = self.readline()
 
-    def _stat (self) :
+            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.
         """
@@ -54,57 +75,20 @@
         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
+    
+    def open (cls) :
+        return open(self.path, 'r')
 
-        self._file.close()
-        self._file = None
+    def reopen (cls) :
+        self.file = self.open()
+        self._stat = self.stat()
 
-    def _reopen (self) :
-        """
-            Re-open, in case the file changed..
-        """
-
-        self._close()
-        self._open()
-
-    def _changed (self) :
+    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
+        return self.stat() != self._stat
 
     def readlines (self, eof_mark=False) :
         """
@@ -120,39 +104,22 @@
                 line = self.readline()
 
             except EOFError :
-                if self._changed() :
-                    log.debug("EOF: file changed: reopen")
-                    self._reopen()
+                if self.changed() :
+                    log.debug("EOF: reopen")
+                    self.reopen()
                     
                     if eof_mark :
-                        # special token
-                        yield None
+                        yield None # special token
+                    
+                    # keep going
+                    continue
 
                 else :
-                    log.debug("EOF: wait")
-                    # done reading
-                    return
+                    log.debug("EOF")
+                    break # wait
 
             else :
-                yield line.strip()
+                yield line
 
     __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()
-