summaryrefslogtreecommitdiff
path: root/paramiko/file.py
diff options
context:
space:
mode:
Diffstat (limited to 'paramiko/file.py')
-rw-r--r--paramiko/file.py440
1 files changed, 440 insertions, 0 deletions
diff --git a/paramiko/file.py b/paramiko/file.py
new file mode 100644
index 0000000..c29e7c4
--- /dev/null
+++ b/paramiko/file.py
@@ -0,0 +1,440 @@
+# 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 __del__(self):
+ self.close()
+
+ 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 self._wbuffer.tell() >= 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,)