diff options
-rw-r--r-- | paramiko/file.py | 47 | ||||
-rw-r--r-- | paramiko/sftp_file.py | 64 |
2 files changed, 78 insertions, 33 deletions
diff --git a/paramiko/file.py b/paramiko/file.py index e3b0a16a..368dd1ee 100644 --- a/paramiko/file.py +++ b/paramiko/file.py @@ -59,7 +59,7 @@ class BufferedFile (ClosingContextManager): def __del__(self): self.close() - + def __iter__(self): """ Returns an iterator that can be used to iterate over the lines in this @@ -119,6 +119,41 @@ class BufferedFile (ClosingContextManager): raise StopIteration return line + def readable(self): + """ + Check if the file can be read from. + + :return: + `True` if the file can be read from. If `False`, :meth`read` will + raise an exception + """ + return (self._flags & self.FLAG_READ) == self.FLAG_READ + + def writable(self): + """ + Check if the file can be written to. + + :return: + `True` if the file can be written to. If `False`, :meth:`write` + will raise an exception + """ + return (self._flags & self.FLAG_WRITE) == self.FLAG_WRITE + + def seekable(self): + """ + Check if the file supports random access. + + :return: + `True` if the file supports random access. If `False`, + :meth:`seek` will raise an exception + """ + return False + + def readinto(self, buff): + data = self.read(len(buff)) + buff[:len(data)] = data + return len(data) + def read(self, size=None): """ Read at most ``size`` bytes from the file (less if we hit the end of the @@ -155,12 +190,12 @@ class BufferedFile (ClosingContextManager): result += new_data self._realpos += len(new_data) self._pos += len(new_data) - return result + return result if size <= len(self._rbuffer): result = self._rbuffer[:size] self._rbuffer = self._rbuffer[size:] self._pos += len(result) - return result + return result while len(self._rbuffer) < size: read_size = size - len(self._rbuffer) if self._flags & self.FLAG_BUFFERED: @@ -176,7 +211,7 @@ class BufferedFile (ClosingContextManager): result = self._rbuffer[:size] self._rbuffer = self._rbuffer[size:] self._pos += len(result) - return result + return result def readline(self, size=None): """ @@ -254,7 +289,7 @@ class BufferedFile (ClosingContextManager): xpos = pos + 1 if (line[pos] == cr_byte_value) and (xpos < len(line)) and (line[xpos] == linefeed_byte_value): xpos += 1 - # if the string was truncated, _rbuffer needs to have the string after + # if the string was truncated, _rbuffer needs to have the string after # the newline character plus the truncated part of the line we stored # earlier in _rbuffer self._rbuffer = line[xpos:] + self._rbuffer if truncated else line[xpos:] @@ -300,7 +335,7 @@ class BufferedFile (ClosingContextManager): If a file is opened in append mode (``'a'`` or ``'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 int offset: position to move to within the file, relative to ``whence``. :param int whence: diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py index d0a37da3..1d070ee0 100644 --- a/paramiko/sftp_file.py +++ b/paramiko/sftp_file.py @@ -64,13 +64,13 @@ class SFTPFile (BufferedFile): def __del__(self): self._close(async=True) - + def close(self): """ Close the file. """ self._close(async=False) - + def _close(self, async=False): # We allow double-close without signaling an error, because real # Python file objects do. However, we must protect against actually @@ -112,7 +112,7 @@ class SFTPFile (BufferedFile): return True # well, we have part of the request. see if another chunk has the rest. return self._data_in_prefetch_requests(buf_offset + buf_size, offset + size - buf_offset - buf_size) - + def _data_in_prefetch_buffers(self, offset): """ if a block of data is present in the prefetch buffers, at the given @@ -129,7 +129,7 @@ class SFTPFile (BufferedFile): # it's not here return None return index - + def _read_prefetch(self, size): """ read data out of the prefetch buffer, if possible. if the data isn't @@ -149,7 +149,7 @@ class SFTPFile (BufferedFile): return None prefetch = self._prefetch_data[offset] del self._prefetch_data[offset] - + buf_offset = self._realpos - offset if buf_offset > 0: self._prefetch_data[offset] = prefetch[:buf_offset] @@ -158,7 +158,7 @@ class SFTPFile (BufferedFile): self._prefetch_data[self._realpos + size] = prefetch[size:] prefetch = prefetch[:size] return prefetch - + def _read(self, size): size = min(size, self.MAX_REQUEST_SIZE) if self._prefetching: @@ -217,6 +217,16 @@ class SFTPFile (BufferedFile): """ self.sftp.sock.setblocking(blocking) + def seekable(self): + """ + Check if the file supports random access. + + :return: + `True` if the file supports random access. If `False`, + :meth:`seek` will raise an exception + """ + return True + def seek(self, offset, whence=0): self.flush() if whence == self.SEEK_SET: @@ -253,7 +263,7 @@ class SFTPFile (BufferedFile): attr = SFTPAttributes() attr.st_mode = mode self.sftp._request(CMD_FSETSTAT, self.handle, attr) - + def chown(self, uid, gid): """ Change the owner (``uid``) and group (``gid``) of this file. As with @@ -294,7 +304,7 @@ class SFTPFile (BufferedFile): Change the size of this file. This usually extends or shrinks the size of the file, just like the ``truncate()`` method on Python file objects. - + :param size: the new size of the file :type size: int or long """ @@ -302,17 +312,17 @@ class SFTPFile (BufferedFile): attr = SFTPAttributes() attr.st_size = size self.sftp._request(CMD_FSETSTAT, self.handle, attr) - + def check(self, hash_algorithm, offset=0, length=0, block_size=0): """ Ask the server for a hash of a section of this file. This can be used to verify a successful upload or download, or for various rsync-like operations. - + The file is hashed from ``offset``, for ``length`` bytes. If ``length`` is 0, the remainder of the file is hashed. Thus, if both ``offset`` and ``length`` are zero, the entire file is hashed. - + Normally, ``block_size`` will be 0 (the default), and this method will return a byte string representing the requested hash (for example, a string of length 16 for MD5, or 20 for SHA-1). If a non-zero @@ -320,12 +330,12 @@ class SFTPFile (BufferedFile): ``offset + length``) of ``block_size`` bytes is computed as a separate hash. The hash results are all concatenated and returned as a single string. - + For example, ``check('sha1', 0, 1024, 512)`` will return a string of length 40. The first 20 bytes will be the SHA-1 of the first 512 bytes of the file, and the last 20 bytes will be the SHA-1 of the next 512 bytes. - + :param str hash_algorithm: the name of the hash algorithm to use (normally ``"sha1"`` or ``"md5"``) @@ -343,13 +353,13 @@ class SFTPFile (BufferedFile): :return: `str` of bytes representing the hash of each block, concatenated together - + :raises IOError: if the server doesn't support the "check-file" extension, or possibly doesn't support the hash algorithm requested - + .. note:: Many (most?) servers don't support this extension yet. - + .. versionadded:: 1.4 """ t, msg = self.sftp._request(CMD_EXTENDED, 'check-file', self.handle, @@ -358,7 +368,7 @@ class SFTPFile (BufferedFile): alg = msg.get_text() data = msg.get_remainder() return data - + def set_pipelined(self, pipelined=True): """ Turn on/off the pipelining of write operations to this file. When @@ -368,24 +378,24 @@ class SFTPFile (BufferedFile): server responses are collected. This means that if there was an error with one of your later writes, an exception might be thrown from within `.close` instead of `.write`. - + By default, files are not pipelined. - + :param bool pipelined: ``True`` if pipelining should be turned on for this file; ``False`` otherwise - + .. versionadded:: 1.5 """ self.pipelined = pipelined - + def prefetch(self): """ Pre-fetch the remaining contents of this file in anticipation of future `.read` calls. If reading the entire file, pre-fetching can dramatically improve the download speed by avoiding roundtrip latency. The file's contents are incrementally buffered in a background thread. - + The prefetched data is stored in a buffer until read via the `.read` method. Once data has been read, it's removed from the buffer. The data may be read in a random order (using `.seek`); chunks of the @@ -403,20 +413,20 @@ class SFTPFile (BufferedFile): n += chunk if len(chunks) > 0: self._start_prefetch(chunks) - + def readv(self, chunks): """ Read a set of blocks from the file by (offset, length). This is more efficient than doing a series of `.seek` and `.read` calls, since the prefetch machinery is used to retrieve all the requested blocks at once. - + :param chunks: a list of (offset, length) tuples indicating which sections of the file to read :type chunks: list(tuple(long, int)) :return: a list of blocks read, in the same order as in ``chunks`` - + .. versionadded:: 1.5.4 """ self.sftp._log(DEBUG, 'readv(%s, %r)' % (hexlify(self.handle), chunks)) @@ -455,7 +465,7 @@ class SFTPFile (BufferedFile): t = threading.Thread(target=self._prefetch_thread, args=(chunks,)) t.setDaemon(True) t.start() - + def _prefetch_thread(self, chunks): # do these read requests in a temporary thread because there may be # a lot of them, so it may block. @@ -481,7 +491,7 @@ class SFTPFile (BufferedFile): del self._prefetch_extents[num] if len(self._prefetch_extents) == 0: self._prefetch_done = True - + def _check_exception(self): """if there's a saved exception, raise & clear it""" if self._saved_exception is not None: |