when a file is open for append, don't stat to get the file position unless the user asks for it explicitly
This commit is contained in:
parent
738e81033a
commit
145ceab54c
|
@ -23,13 +23,6 @@ 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):
|
||||
|
@ -44,6 +37,14 @@ class BufferedFile (object):
|
|||
SEEK_CUR = 1
|
||||
SEEK_END = 2
|
||||
|
||||
FLAG_READ = 0x1
|
||||
FLAG_WRITE = 0x2
|
||||
FLAG_APPEND = 0x4
|
||||
FLAG_BINARY = 0x10
|
||||
FLAG_BUFFERED = 0x20
|
||||
FLAG_LINE_BUFFERED = 0x40
|
||||
FLAG_UNIVERSAL_NEWLINE = 0x80
|
||||
|
||||
def __init__(self):
|
||||
self.newlines = None
|
||||
self._flags = 0
|
||||
|
@ -123,7 +124,7 @@ class BufferedFile (object):
|
|||
"""
|
||||
if self._closed:
|
||||
raise IOError('File is closed')
|
||||
if not (self._flags & _FLAG_READ):
|
||||
if not (self._flags & self.FLAG_READ):
|
||||
raise IOError('File not open for reading')
|
||||
if (size is None) or (size < 0):
|
||||
# go for broke
|
||||
|
@ -148,7 +149,7 @@ class BufferedFile (object):
|
|||
return result
|
||||
while len(self._rbuffer) < size:
|
||||
read_size = size - len(self._rbuffer)
|
||||
if self._flags & _FLAG_BUFFERED:
|
||||
if self._flags & self.FLAG_BUFFERED:
|
||||
read_size = max(self._bufsize, read_size)
|
||||
try:
|
||||
new_data = self._read(read_size)
|
||||
|
@ -184,11 +185,11 @@ class BufferedFile (object):
|
|||
# it's almost silly how complex this function is.
|
||||
if self._closed:
|
||||
raise IOError('File is closed')
|
||||
if not (self._flags & _FLAG_READ):
|
||||
if not (self._flags & self.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):
|
||||
if self._at_trailing_cr and (self._flags & self.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':
|
||||
|
@ -209,7 +210,7 @@ class BufferedFile (object):
|
|||
n = size - len(line)
|
||||
else:
|
||||
n = self._bufsize
|
||||
if ('\n' in line) or ((self._flags & _FLAG_UNIVERSAL_NEWLINE) and ('\r' in line)):
|
||||
if ('\n' in line) or ((self._flags & self.FLAG_UNIVERSAL_NEWLINE) and ('\r' in line)):
|
||||
break
|
||||
try:
|
||||
new_data = self._read(n)
|
||||
|
@ -223,7 +224,7 @@ class BufferedFile (object):
|
|||
self._realpos += len(new_data)
|
||||
# find the newline
|
||||
pos = line.find('\n')
|
||||
if self._flags & _FLAG_UNIVERSAL_NEWLINE:
|
||||
if self._flags & self.FLAG_UNIVERSAL_NEWLINE:
|
||||
rpos = line.find('\r')
|
||||
if (rpos >= 0) and ((rpos < pos) or (pos < 0)):
|
||||
pos = rpos
|
||||
|
@ -295,6 +296,8 @@ class BufferedFile (object):
|
|||
@return: file position (in bytes).
|
||||
@rtype: int
|
||||
"""
|
||||
if self._flags & self.FLAG_APPEND:
|
||||
return self._get_size()
|
||||
return self._pos
|
||||
|
||||
def write(self, data):
|
||||
|
@ -309,13 +312,13 @@ class BufferedFile (object):
|
|||
"""
|
||||
if self._closed:
|
||||
raise IOError('File is closed')
|
||||
if not (self._flags & _FLAG_WRITE):
|
||||
if not (self._flags & self.FLAG_WRITE):
|
||||
raise IOError('File not open for writing')
|
||||
if not (self._flags & _FLAG_BUFFERED):
|
||||
if not (self._flags & self.FLAG_BUFFERED):
|
||||
self._write_all(data)
|
||||
return
|
||||
self._wbuffer.write(data)
|
||||
if self._flags & _FLAG_LINE_BUFFERED:
|
||||
if self._flags & self.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:
|
||||
|
@ -397,22 +400,21 @@ class BufferedFile (object):
|
|||
# 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
|
||||
self._flags |= self.FLAG_BUFFERED | self.FLAG_LINE_BUFFERED
|
||||
elif bufsize > 1:
|
||||
self._bufsize = bufsize
|
||||
self._flags |= _FLAG_BUFFERED
|
||||
self._flags |= self.FLAG_BUFFERED
|
||||
if ('r' in mode) or ('+' in mode):
|
||||
self._flags |= _FLAG_READ
|
||||
self._flags |= self.FLAG_READ
|
||||
if ('w' in mode) or ('+' in mode):
|
||||
self._flags |= _FLAG_WRITE
|
||||
self._flags |= self.FLAG_WRITE
|
||||
if ('a' in mode):
|
||||
self._flags |= _FLAG_WRITE | _FLAG_APPEND
|
||||
self._size = self._get_size()
|
||||
self._pos = self._realpos = self._size
|
||||
self._flags |= self.FLAG_WRITE | self.FLAG_APPEND
|
||||
self._pos = self._realpos = -1
|
||||
if ('b' in mode):
|
||||
self._flags |= _FLAG_BINARY
|
||||
self._flags |= self.FLAG_BINARY
|
||||
if ('U' in mode):
|
||||
self._flags |= _FLAG_UNIVERSAL_NEWLINE
|
||||
self._flags |= self.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>
|
||||
|
@ -424,9 +426,9 @@ class BufferedFile (object):
|
|||
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
|
||||
if self._flags & self.FLAG_APPEND:
|
||||
# even if we used to know our seek position, we don't now.
|
||||
self._pos = self._realpos = -1
|
||||
else:
|
||||
self._pos += count
|
||||
self._realpos += count
|
||||
|
@ -436,7 +438,7 @@ class BufferedFile (object):
|
|||
# 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):
|
||||
if not (self._flags & self.FLAG_UNIVERSAL_NEWLINE):
|
||||
return
|
||||
if self.newlines is None:
|
||||
self.newlines = newline
|
||||
|
|
|
@ -159,8 +159,11 @@ class SFTPFile (BufferedFile):
|
|||
def _write(self, data):
|
||||
# may write less than requested if it would exceed max packet size
|
||||
chunk = min(len(data), self.MAX_REQUEST_SIZE)
|
||||
req = self.sftp._async_request(type(None), CMD_WRITE, self.handle, long(self._realpos),
|
||||
str(data[:chunk]))
|
||||
if self._flags & self.FLAG_APPEND:
|
||||
pos = 0
|
||||
else:
|
||||
pos = self._realpos
|
||||
req = self.sftp._async_request(type(None), CMD_WRITE, self.handle, long(pos), str(data[:chunk]))
|
||||
if not self.pipelined or self.sftp.sock.recv_ready():
|
||||
t, msg = self.sftp._read_response(req)
|
||||
if t != CMD_STATUS:
|
||||
|
@ -204,6 +207,10 @@ class SFTPFile (BufferedFile):
|
|||
|
||||
def seek(self, offset, whence=0):
|
||||
self.flush()
|
||||
if (self._flags & self.FLAG_APPEND) and (self._realpos == -1) and (whence != self.SEEK_END):
|
||||
# this is still legal for O_RDWR ('a+'), but we need to figure out
|
||||
# where we are -- we lost track of it during writes.
|
||||
self._realpos = self._pos = self._get_size()
|
||||
if whence == self.SEEK_SET:
|
||||
self._realpos = self._pos = offset
|
||||
elif whence == self.SEEK_CUR:
|
||||
|
|
|
@ -648,3 +648,24 @@ class SFTPTest (unittest.TestCase):
|
|||
f.close()
|
||||
finally:
|
||||
sftp.unlink(FOLDER + '/zero')
|
||||
|
||||
def test_M_seek_append(self):
|
||||
"""
|
||||
verify that seek does't affect writes during append.
|
||||
"""
|
||||
f = sftp.open(FOLDER + '/append.txt', 'a')
|
||||
try:
|
||||
f.write('first line\nsecond line\n')
|
||||
f.seek(11, f.SEEK_SET)
|
||||
f.write('third line\n')
|
||||
f.close()
|
||||
|
||||
f = sftp.open(FOLDER + '/append.txt', 'r')
|
||||
self.assertEqual(f.stat().st_size, 34)
|
||||
self.assertEqual(f.readline(), 'first line\n')
|
||||
self.assertEqual(f.readline(), 'second line\n')
|
||||
self.assertEqual(f.readline(), 'third line\n')
|
||||
f.close()
|
||||
finally:
|
||||
sftp.remove(FOLDER + '/append.txt')
|
||||
|
||||
|
|
Loading…
Reference in New Issue