438 lines
15 KiB
Python
438 lines
15 KiB
Python
#!/usr/bin/python
|
|
|
|
# Copyright (C) 2003-2005 Robey Pointer <robey@lag.net>
|
|
#
|
|
# This file is part of paramiko.
|
|
#
|
|
# Paramiko is free software; you can redistribute it and/or modify it under the
|
|
# terms of the GNU Lesser General Public License as published by the Free
|
|
# Software Foundation; either version 2.1 of the License, or (at your option)
|
|
# any later version.
|
|
#
|
|
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public License
|
|
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
|
|
"""
|
|
BufferedFile.
|
|
"""
|
|
|
|
from cStringIO import StringIO
|
|
|
|
_FLAG_READ = 0x1
|
|
_FLAG_WRITE = 0x2
|
|
_FLAG_APPEND = 0x4
|
|
_FLAG_BINARY = 0x10
|
|
_FLAG_BUFFERED = 0x20
|
|
_FLAG_LINE_BUFFERED = 0x40
|
|
_FLAG_UNIVERSAL_NEWLINE = 0x80
|
|
|
|
class BufferedFile (object):
|
|
"""
|
|
Reusable base class to implement python-style file buffering around a
|
|
simpler stream.
|
|
"""
|
|
|
|
_DEFAULT_BUFSIZE = 8192
|
|
|
|
SEEK_SET = 0
|
|
SEEK_CUR = 1
|
|
SEEK_END = 2
|
|
|
|
def __init__(self):
|
|
self._flags = 0
|
|
self._bufsize = self._DEFAULT_BUFSIZE
|
|
self._wbuffer = StringIO()
|
|
self._rbuffer = ''
|
|
self._at_trailing_cr = False
|
|
self._closed = False
|
|
# pos - position within the file, according to the user
|
|
# realpos - position according the OS
|
|
# (these may be different because we buffer for line reading)
|
|
self._pos = self._realpos = 0
|
|
|
|
def __iter__(self):
|
|
"""
|
|
Returns an iterator that can be used to iterate over the lines in this
|
|
file. This iterator happens to return the file itself, since a file is
|
|
its own iterator.
|
|
|
|
@raise ValueError: if the file is closed.
|
|
|
|
@return: an interator.
|
|
@rtype: iterator
|
|
"""
|
|
if self._closed:
|
|
raise ValueError('I/O operation on closed file')
|
|
return self
|
|
|
|
def close(self):
|
|
"""
|
|
Close the file. Future read and write operations will fail.
|
|
"""
|
|
self.flush()
|
|
self._closed = True
|
|
|
|
def flush(self):
|
|
"""
|
|
Write out any data in the write buffer. This may do nothing if write
|
|
buffering is not turned on.
|
|
"""
|
|
self._write_all(self._wbuffer.getvalue())
|
|
self._wbuffer = StringIO()
|
|
return
|
|
|
|
def next(self):
|
|
"""
|
|
Returns the next line from the input, or raises L{StopIteration} when
|
|
EOF is hit. Unlike python file objects, it's okay to mix calls to
|
|
C{next} and L{readline}.
|
|
|
|
@raise StopIteration: when the end of the file is reached.
|
|
|
|
@return: a line read from the file.
|
|
@rtype: str
|
|
"""
|
|
line = self.readline()
|
|
if not line:
|
|
raise StopIteration
|
|
return line
|
|
|
|
def read(self, size=None):
|
|
"""
|
|
Read at most C{size} bytes from the file (less if we hit the end of the
|
|
file first). If the C{size} argument is negative or omitted, read all
|
|
the remaining data in the file.
|
|
|
|
@param size: maximum number of bytes to read.
|
|
@type size: int
|
|
@return: data read from the file, or an empty string if EOF was
|
|
encountered immediately.
|
|
@rtype: str
|
|
"""
|
|
if self._closed:
|
|
raise IOError('File is closed')
|
|
if not (self._flags & _FLAG_READ):
|
|
raise IOError('File not open for reading')
|
|
if (size is None) or (size < 0):
|
|
# go for broke
|
|
result = self._rbuffer
|
|
self._rbuffer = ''
|
|
self._pos += len(result)
|
|
while True:
|
|
try:
|
|
new_data = self._read(self._DEFAULT_BUFSIZE)
|
|
except EOFError:
|
|
new_data = None
|
|
if (new_data is None) or (len(new_data) == 0):
|
|
break
|
|
result += new_data
|
|
self._realpos += len(new_data)
|
|
self._pos += len(new_data)
|
|
return result
|
|
if size <= len(self._rbuffer):
|
|
result = self._rbuffer[:size]
|
|
self._rbuffer = self._rbuffer[size:]
|
|
self._pos += len(result)
|
|
return result
|
|
while len(self._rbuffer) < size:
|
|
try:
|
|
new_data = self._read(max(self._bufsize, size - len(self._rbuffer)))
|
|
except EOFError:
|
|
new_data = None
|
|
if (new_data is None) or (len(new_data) == 0):
|
|
break
|
|
self._rbuffer += new_data
|
|
self._realpos += len(new_data)
|
|
result = self._rbuffer[:size]
|
|
self._rbuffer = self._rbuffer[size:]
|
|
self._pos += len(result)
|
|
return result
|
|
|
|
def readline(self, size=None):
|
|
"""
|
|
Read one entire line from the file. A trailing newline character is
|
|
kept in the string (but may be absent when a file ends with an
|
|
incomplete line). If the size argument is present and non-negative, it
|
|
is a maximum byte count (including the trailing newline) and an
|
|
incomplete line may be returned. An empty string is returned only when
|
|
EOF is encountered immediately.
|
|
|
|
@note: Unlike stdio's C{fgets()}, the returned string contains null
|
|
characters (C{'\\0'}) if they occurred in the input.
|
|
|
|
@param size: maximum length of returned string.
|
|
@type size: int
|
|
@return: next line of the file, or an empty string if the end of the
|
|
file has been reached.
|
|
@rtype: str
|
|
"""
|
|
# it's almost silly how complex this function is.
|
|
if self._closed:
|
|
raise IOError('File is closed')
|
|
if not (self._flags & _FLAG_READ):
|
|
raise IOError('File not open for reading')
|
|
line = self._rbuffer
|
|
while True:
|
|
if self._at_trailing_cr and (self._flags & _FLAG_UNIVERSAL_NEWLINE) and (len(line) > 0):
|
|
# edge case: the newline may be '\r\n' and we may have read
|
|
# only the first '\r' last time.
|
|
if line[0] == '\n':
|
|
line = line[1:]
|
|
self._record_newline('\r\n')
|
|
else:
|
|
self._record_newline('\r')
|
|
self._at_trailing_cr = False
|
|
# check size before looking for a linefeed, in case we already have
|
|
# enough.
|
|
if (size is not None) and (size >= 0):
|
|
if len(line) >= size:
|
|
# truncate line and return
|
|
self._rbuffer = line[size:]
|
|
line = line[:size]
|
|
self._pos += len(line)
|
|
return line
|
|
n = size - len(line)
|
|
else:
|
|
n = self._DEFAULT_BUFSIZE
|
|
if ('\n' in line) or ((self._flags & _FLAG_UNIVERSAL_NEWLINE) and ('\r' in line)):
|
|
break
|
|
try:
|
|
new_data = self._read(n)
|
|
except EOFError:
|
|
new_data = None
|
|
if (new_data is None) or (len(new_data) == 0):
|
|
self._rbuffer = ''
|
|
self._pos += len(line)
|
|
return line
|
|
line += new_data
|
|
self._realpos += len(new_data)
|
|
# find the newline
|
|
pos = line.find('\n')
|
|
if self._flags & _FLAG_UNIVERSAL_NEWLINE:
|
|
rpos = line.find('\r')
|
|
if (rpos >= 0) and ((rpos < pos) or (pos < 0)):
|
|
pos = rpos
|
|
xpos = pos + 1
|
|
if (line[pos] == '\r') and (xpos < len(line)) and (line[xpos] == '\n'):
|
|
xpos += 1
|
|
self._rbuffer = line[xpos:]
|
|
lf = line[pos:xpos]
|
|
line = line[:pos] + '\n'
|
|
if (len(self._rbuffer) == 0) and (lf == '\r'):
|
|
# we could read the line up to a '\r' and there could still be a
|
|
# '\n' following that we read next time. note that and eat it.
|
|
self._at_trailing_cr = True
|
|
else:
|
|
self._record_newline(lf)
|
|
self._pos += len(line)
|
|
return line
|
|
|
|
def readlines(self, sizehint=None):
|
|
"""
|
|
Read all remaining lines using L{readline} and return them as a list.
|
|
If the optional C{sizehint} argument is present, instead of reading up
|
|
to EOF, whole lines totalling approximately sizehint bytes (possibly
|
|
after rounding up to an internal buffer size) are read.
|
|
|
|
@param sizehint: desired maximum number of bytes to read.
|
|
@type sizehint: int
|
|
@return: list of lines read from the file.
|
|
@rtype: list
|
|
"""
|
|
lines = []
|
|
bytes = 0
|
|
while 1:
|
|
line = self.readline()
|
|
if len(line) == 0:
|
|
break
|
|
lines.append(line)
|
|
bytes += len(line)
|
|
if (sizehint is not None) and (bytes >= sizehint):
|
|
break
|
|
return lines
|
|
|
|
def seek(self, offset, whence=0):
|
|
"""
|
|
Set the file's current position, like stdio's C{fseek}. Not all file
|
|
objects support seeking.
|
|
|
|
@note: If a file is opened in append mode (C{'a'} or C{'a+'}), any seek
|
|
operations will be undone at the next write (as the file position
|
|
will move back to the end of the file).
|
|
|
|
@param offset: position to move to within the file, relative to
|
|
C{whence}.
|
|
@type offset: int
|
|
@param whence: type of movement: 0 = absolute; 1 = relative to the
|
|
current position; 2 = relative to the end of the file.
|
|
@type whence: int
|
|
|
|
@raise IOError: if the file doesn't support random access.
|
|
"""
|
|
raise IOError('File does not support seeking.')
|
|
|
|
def tell(self):
|
|
"""
|
|
Return the file's current position. This may not be accurate or
|
|
useful if the underlying file doesn't support random access, or was
|
|
opened in append mode.
|
|
|
|
@return: file position (in bytes).
|
|
@rtype: int
|
|
"""
|
|
return self._pos
|
|
|
|
def write(self, data):
|
|
"""
|
|
Write data to the file. If write buffering is on (C{bufsize} was
|
|
specified and non-zero), some or all of the data may not actually be
|
|
written yet. (Use L{flush} or L{close} to force buffered data to be
|
|
written out.)
|
|
|
|
@param data: data to write.
|
|
@type data: str
|
|
"""
|
|
if self._closed:
|
|
raise IOError('File is closed')
|
|
if not (self._flags & _FLAG_WRITE):
|
|
raise IOError('File not open for writing')
|
|
if not (self._flags & _FLAG_BUFFERED):
|
|
self._write_all(data)
|
|
return
|
|
self._wbuffer.write(data)
|
|
if self._flags & _FLAG_LINE_BUFFERED:
|
|
# only scan the new data for linefeed, to avoid wasting time.
|
|
last_newline_pos = data.rfind('\n')
|
|
if last_newline_pos >= 0:
|
|
wbuf = self._wbuffer.getvalue()
|
|
last_newline_pos += len(wbuf) - len(data)
|
|
self._write_all(wbuf[:last_newline_pos + 1])
|
|
self._wbuffer = StringIO()
|
|
self._wbuffer.write(wbuf[last_newline_pos + 1:])
|
|
return
|
|
# even if we're line buffering, if the buffer has grown past the
|
|
# buffer size, force a flush.
|
|
if len(self._wbuffer.getvalue()) >= self._bufsize:
|
|
self.flush()
|
|
return
|
|
|
|
def writelines(self, sequence):
|
|
"""
|
|
Write a sequence of strings to the file. The sequence can be any
|
|
iterable object producing strings, typically a list of strings. (The
|
|
name is intended to match L{readlines}; C{writelines} does not add line
|
|
separators.)
|
|
|
|
@param sequence: an iterable sequence of strings.
|
|
@type sequence: sequence
|
|
"""
|
|
for line in sequence:
|
|
self.write(line)
|
|
return
|
|
|
|
def xreadlines(self):
|
|
"""
|
|
Identical to C{iter(f)}. This is a deprecated file interface that
|
|
predates python iterator support.
|
|
|
|
@return: an iterator.
|
|
@rtype: iterator
|
|
"""
|
|
return self
|
|
|
|
|
|
### overrides...
|
|
|
|
|
|
def _read(self, size):
|
|
"""
|
|
I{(subclass override)}
|
|
Read data from the stream. Return C{None} or raise C{EOFError} to
|
|
indicate EOF.
|
|
"""
|
|
raise EOFError()
|
|
|
|
def _write(self, data):
|
|
"""
|
|
I{(subclass override)}
|
|
Write data into the stream.
|
|
"""
|
|
raise IOError('write not implemented')
|
|
|
|
def _get_size(self):
|
|
"""
|
|
I{(subclass override)}
|
|
Return the size of the file. This is called from within L{_set_mode}
|
|
if the file is opened in append mode, so the file position can be
|
|
tracked and L{seek} and L{tell} will work correctly. If the file is
|
|
a stream that can't be randomly accessed, you don't need to override
|
|
this method,
|
|
"""
|
|
return 0
|
|
|
|
|
|
### internals...
|
|
|
|
|
|
def _set_mode(self, mode='r', bufsize=-1):
|
|
"""
|
|
Subclasses call this method to initialize the BufferedFile.
|
|
"""
|
|
if bufsize == 1:
|
|
# apparently, line buffering only affects writes. reads are only
|
|
# buffered if you call readline (directly or indirectly: iterating
|
|
# over a file will indirectly call readline).
|
|
self._flags |= _FLAG_BUFFERED | _FLAG_LINE_BUFFERED
|
|
elif bufsize > 1:
|
|
self._bufsize = bufsize
|
|
self._flags |= _FLAG_BUFFERED
|
|
if ('r' in mode) or ('+' in mode):
|
|
self._flags |= _FLAG_READ
|
|
if ('w' in mode) or ('+' in mode):
|
|
self._flags |= _FLAG_WRITE
|
|
if ('a' in mode):
|
|
self._flags |= _FLAG_WRITE | _FLAG_APPEND
|
|
self._size = self._get_size()
|
|
self._pos = self._realpos = self._size
|
|
if ('b' in mode):
|
|
self._flags |= _FLAG_BINARY
|
|
if ('U' in mode):
|
|
self._flags |= _FLAG_UNIVERSAL_NEWLINE
|
|
# built-in file objects have this attribute to store which kinds of
|
|
# line terminations they've seen:
|
|
# <http://www.python.org/doc/current/lib/built-in-funcs.html>
|
|
self.newlines = None
|
|
|
|
def _write_all(self, data):
|
|
# the underlying stream may be something that does partial writes (like
|
|
# a socket).
|
|
while len(data) > 0:
|
|
count = self._write(data)
|
|
data = data[count:]
|
|
if self._flags & _FLAG_APPEND:
|
|
self._size += count
|
|
self._pos = self._realpos = self._size
|
|
else:
|
|
self._pos += count
|
|
self._realpos += count
|
|
return None
|
|
|
|
def _record_newline(self, newline):
|
|
# silliness about tracking what kinds of newlines we've seen.
|
|
# i don't understand why it can be None, a string, or a tuple, instead
|
|
# of just always being a tuple, but we'll emulate that behavior anyway.
|
|
if not (self._flags & _FLAG_UNIVERSAL_NEWLINE):
|
|
return
|
|
if self.newlines is None:
|
|
self.newlines = newline
|
|
elif (type(self.newlines) is str) and (self.newlines != newline):
|
|
self.newlines = (self.newlines, newline)
|
|
elif newline not in self.newlines:
|
|
self.newlines += (newline,)
|