"""
`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()