|
|
@@ -18,9 +18,9 @@ namespace Renci.SshNet.Sftp
|
|
|
|
|
|
// Buffer information.
|
|
|
private readonly int _readBufferSize;
|
|
|
- private readonly byte[] _readBuffer;
|
|
|
+ private byte[] _readBuffer;
|
|
|
private readonly int _writeBufferSize;
|
|
|
- private readonly byte[] _writeBuffer;
|
|
|
+ private byte[] _writeBuffer;
|
|
|
private int _bufferPosition;
|
|
|
private int _bufferLen;
|
|
|
private long _position;
|
|
|
@@ -28,7 +28,6 @@ namespace Renci.SshNet.Sftp
|
|
|
private bool _canRead;
|
|
|
private bool _canSeek;
|
|
|
private bool _canWrite;
|
|
|
- private ulong _serverFilePosition;
|
|
|
|
|
|
private readonly object _lock = new object();
|
|
|
|
|
|
@@ -254,15 +253,12 @@ namespace Renci.SshNet.Sftp
|
|
|
// or SSH_FXP_WRITE message
|
|
|
|
|
|
_readBufferSize = (int) session.CalculateOptimalReadLength((uint) bufferSize);
|
|
|
- _readBuffer = new byte[_readBufferSize];
|
|
|
_writeBufferSize = (int) session.CalculateOptimalWriteLength((uint) bufferSize, _handle);
|
|
|
- _writeBuffer = new byte[_writeBufferSize];
|
|
|
|
|
|
if (mode == FileMode.Append)
|
|
|
{
|
|
|
var attributes = _session.RequestFStat(_handle, false);
|
|
|
_position = attributes.Size;
|
|
|
- _serverFilePosition = (ulong) attributes.Size;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -314,6 +310,22 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <exception cref="IOException">An I/O error occurs. </exception>
|
|
|
/// <exception cref="NotSupportedException">The stream does not support reading. </exception>
|
|
|
/// <exception cref="ObjectDisposedException">Methods were called after the stream was closed. </exception>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// This method attempts to read up to <paramref name="count"/> bytes. This either from the buffer, from the
|
|
|
+ /// server (using one or more <c>SSH_FXP_READ</c> requests) or using a combination of both.
|
|
|
+ /// </para>
|
|
|
+ /// <para>
|
|
|
+ /// The read loop is interrupted when either <paramref name="count"/> bytes are read, the server returns zero
|
|
|
+ /// bytes (EOF) or less bytes than the read buffer size.
|
|
|
+ /// </para>
|
|
|
+ /// <para>
|
|
|
+ /// When a server returns less number of bytes than the read buffer size, this <c>may</c> indicate that EOF has
|
|
|
+ /// been reached. A subsequent (<c>SSH_FXP_READ</c>) server request is necessary to make sure EOF has effectively
|
|
|
+ /// been reached. Breaking out of the read loop avoids reading from the server twice to determine EOF: once in
|
|
|
+ /// the read loop, and once upon the next <see cref="Read"/> or <see cref="ReadByte"/> invocation.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
public override int Read(byte[] buffer, int offset, int count)
|
|
|
{
|
|
|
var readLen = 0;
|
|
|
@@ -342,60 +354,82 @@ namespace Renci.SshNet.Sftp
|
|
|
var bytesAvailableInBuffer = _bufferLen - _bufferPosition;
|
|
|
if (bytesAvailableInBuffer <= 0)
|
|
|
{
|
|
|
- _bufferPosition = 0;
|
|
|
- _bufferLen = 0;
|
|
|
-
|
|
|
var data = _session.RequestRead(_handle, (ulong) _position, (uint) _readBufferSize);
|
|
|
|
|
|
- // TODO: don't we need to take into account the number of bytes read (data.Length) ?
|
|
|
- _serverFilePosition = (ulong) _position;
|
|
|
-
|
|
|
if (data.Length == 0)
|
|
|
{
|
|
|
+ _bufferPosition = 0;
|
|
|
+ _bufferLen = 0;
|
|
|
+
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- // determine number of bytes that we can read into caller-provided buffer
|
|
|
- var bytesToWriteToCallerBuffer = Math.Min(data.Length, count);
|
|
|
+ var bytesToWriteToCallerBuffer = count;
|
|
|
+ if (bytesToWriteToCallerBuffer >= data.Length)
|
|
|
+ {
|
|
|
+ // write all data read to caller-provided buffer
|
|
|
+ bytesToWriteToCallerBuffer = data.Length;
|
|
|
+ // reset buffer since we will skip buffering
|
|
|
+ _bufferPosition = 0;
|
|
|
+ _bufferLen = 0;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // determine number of bytes that we should write into read buffer
|
|
|
+ var bytesToWriteToReadBuffer = data.Length - bytesToWriteToCallerBuffer;
|
|
|
+ // write remaining bytes to read buffer
|
|
|
+ Buffer.BlockCopy(data, count, GetOrCreateReadBuffer(), 0, bytesToWriteToReadBuffer);
|
|
|
+ // update position in read buffer
|
|
|
+ _bufferPosition = 0;
|
|
|
+ // update number of bytes in read buffer
|
|
|
+ _bufferLen = bytesToWriteToReadBuffer;
|
|
|
+ }
|
|
|
+
|
|
|
// write bytes to caller-provided buffer
|
|
|
Buffer.BlockCopy(data, 0, buffer, offset, bytesToWriteToCallerBuffer);
|
|
|
- // advance offset to start writing bytes into caller-provided buffer
|
|
|
- offset += bytesToWriteToCallerBuffer;
|
|
|
- // update number of bytes left to read
|
|
|
- count -= bytesToWriteToCallerBuffer;
|
|
|
- // record total number of bytes read into caller-provided buffer
|
|
|
- readLen += bytesToWriteToCallerBuffer;
|
|
|
// update stream position
|
|
|
_position += bytesToWriteToCallerBuffer;
|
|
|
+ // record total number of bytes read into caller-provided buffer
|
|
|
+ readLen += bytesToWriteToCallerBuffer;
|
|
|
|
|
|
- if (data.Length > bytesToWriteToCallerBuffer)
|
|
|
+ // break out of the read loop when the server returned less than the request number of bytes
|
|
|
+ // as that *may* indicate that we've reached EOF
|
|
|
+ //
|
|
|
+ // doing this avoids reading from server twice to determine EOF: once in the read loop, and
|
|
|
+ // once upon the next Read or ReadByte invocation by the caller
|
|
|
+ if (data.Length < _readBufferSize)
|
|
|
{
|
|
|
- // copy remaining bytes to read buffer
|
|
|
- _bufferLen = data.Length - bytesToWriteToCallerBuffer;
|
|
|
- Buffer.BlockCopy(data, bytesToWriteToCallerBuffer, _readBuffer, 0, _bufferLen);
|
|
|
+ break;
|
|
|
}
|
|
|
+
|
|
|
+ // advance offset to start writing bytes into caller-provided buffer
|
|
|
+ offset += bytesToWriteToCallerBuffer;
|
|
|
+ // update number of bytes left to read into caller-provided buffer
|
|
|
+ count -= bytesToWriteToCallerBuffer;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- // determine number of bytes that we can write from read buffer to caller-provided buffer
|
|
|
- var bytesToWriteToCallerBuffer = Math.Min(bytesAvailableInBuffer, count);
|
|
|
+ // limit the number of bytes to use from read buffer to the caller-request number of bytes
|
|
|
+ if (bytesAvailableInBuffer > count)
|
|
|
+ bytesAvailableInBuffer = count;
|
|
|
+
|
|
|
// copy data from read buffer to the caller-provided buffer
|
|
|
- Buffer.BlockCopy(_readBuffer, _bufferPosition, buffer, offset, bytesToWriteToCallerBuffer);
|
|
|
+ Buffer.BlockCopy(GetOrCreateReadBuffer(), _bufferPosition, buffer, offset, bytesAvailableInBuffer);
|
|
|
// update position in read buffer
|
|
|
- _bufferPosition += bytesToWriteToCallerBuffer;
|
|
|
+ _bufferPosition += bytesAvailableInBuffer;
|
|
|
+ // update stream position
|
|
|
+ _position += bytesAvailableInBuffer;
|
|
|
+ // record total number of bytes read into caller-provided buffer
|
|
|
+ readLen += bytesAvailableInBuffer;
|
|
|
// advance offset to start writing bytes into caller-provided buffer
|
|
|
offset += bytesAvailableInBuffer;
|
|
|
// update number of bytes left to read
|
|
|
- count -= bytesToWriteToCallerBuffer;
|
|
|
- // record total number of bytes read into caller-provided buffer
|
|
|
- readLen += bytesToWriteToCallerBuffer;
|
|
|
- // update stream position
|
|
|
- _position += bytesToWriteToCallerBuffer;
|
|
|
+ count -= bytesAvailableInBuffer;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Return the number of bytes that were read to the caller.
|
|
|
+ // return the number of bytes that were read to the caller.
|
|
|
return readLen;
|
|
|
}
|
|
|
|
|
|
@@ -418,28 +452,32 @@ namespace Renci.SshNet.Sftp
|
|
|
// Setup the object for reading.
|
|
|
SetupRead();
|
|
|
|
|
|
+ byte[] readBuffer;
|
|
|
+
|
|
|
// Read more data into the internal buffer if necessary.
|
|
|
if (_bufferPosition >= _bufferLen)
|
|
|
{
|
|
|
- _bufferPosition = 0;
|
|
|
-
|
|
|
var data = _session.RequestRead(_handle, (ulong) _position, (uint) _readBufferSize);
|
|
|
-
|
|
|
- _bufferLen = data.Length;
|
|
|
- _serverFilePosition = (ulong) _position;
|
|
|
-
|
|
|
- if (_bufferLen == 0)
|
|
|
+ if (data.Length == 0)
|
|
|
{
|
|
|
// We've reached EOF.
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
- Buffer.BlockCopy(data, 0, _readBuffer, 0, _bufferLen);
|
|
|
+ readBuffer = GetOrCreateReadBuffer();
|
|
|
+ Buffer.BlockCopy(data, 0, readBuffer, 0, data.Length);
|
|
|
+
|
|
|
+ _bufferPosition = 0;
|
|
|
+ _bufferLen = data.Length;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ readBuffer = GetOrCreateReadBuffer();
|
|
|
}
|
|
|
|
|
|
// Extract the next byte from the buffer.
|
|
|
++_position;
|
|
|
- return _readBuffer[_bufferPosition++];
|
|
|
+ return readBuffer[_bufferPosition++];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -501,7 +539,6 @@ namespace Renci.SshNet.Sftp
|
|
|
throw new EndOfStreamException("End of stream.");
|
|
|
}
|
|
|
_position = newPosn;
|
|
|
- _serverFilePosition = (ulong)newPosn;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
@@ -510,8 +547,7 @@ namespace Renci.SshNet.Sftp
|
|
|
if (origin == SeekOrigin.Begin)
|
|
|
{
|
|
|
newPosn = _position - _bufferPosition;
|
|
|
- if (offset >= newPosn && offset <
|
|
|
- (newPosn + _bufferLen))
|
|
|
+ if (offset >= newPosn && offset < (newPosn + _bufferLen))
|
|
|
{
|
|
|
_bufferPosition = (int)(offset - newPosn);
|
|
|
_position = offset;
|
|
|
@@ -524,8 +560,7 @@ namespace Renci.SshNet.Sftp
|
|
|
if (newPosn >= (_position - _bufferPosition) &&
|
|
|
newPosn < (_position - _bufferPosition + _bufferLen))
|
|
|
{
|
|
|
- _bufferPosition =
|
|
|
- (int)(newPosn - (_position - _bufferPosition));
|
|
|
+ _bufferPosition = (int) (newPosn - (_position - _bufferPosition));
|
|
|
_position = newPosn;
|
|
|
return _position;
|
|
|
}
|
|
|
@@ -645,14 +680,13 @@ namespace Renci.SshNet.Sftp
|
|
|
{
|
|
|
using (var wait = new AutoResetEvent(false))
|
|
|
{
|
|
|
- _session.RequestWrite(_handle, _serverFilePosition, buffer, offset, tempLen, wait);
|
|
|
- _serverFilePosition += (ulong) tempLen;
|
|
|
+ _session.RequestWrite(_handle, (ulong) _position, buffer, offset, tempLen, wait);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// No: copy the data to the write buffer first.
|
|
|
- Buffer.BlockCopy(buffer, offset, _writeBuffer, _bufferPosition, tempLen);
|
|
|
+ Buffer.BlockCopy(buffer, offset, GetOrCreateWriteBuffer(), _bufferPosition, tempLen);
|
|
|
_bufferPosition += tempLen;
|
|
|
}
|
|
|
|
|
|
@@ -668,8 +702,7 @@ namespace Renci.SshNet.Sftp
|
|
|
{
|
|
|
using (var wait = new AutoResetEvent(false))
|
|
|
{
|
|
|
- _session.RequestWrite(_handle, _serverFilePosition, _writeBuffer, 0, _bufferPosition, wait);
|
|
|
- _serverFilePosition += (ulong) _bufferPosition;
|
|
|
+ _session.RequestWrite(_handle, (ulong) (_position - _bufferPosition), GetOrCreateWriteBuffer(), 0, _bufferPosition, wait);
|
|
|
}
|
|
|
|
|
|
_bufferPosition = 0;
|
|
|
@@ -694,20 +727,21 @@ namespace Renci.SshNet.Sftp
|
|
|
// Setup the object for writing.
|
|
|
SetupWrite();
|
|
|
|
|
|
+ var writeBuffer = GetOrCreateWriteBuffer();
|
|
|
+
|
|
|
// Flush the current buffer if it is full.
|
|
|
if (_bufferPosition >= _writeBufferSize)
|
|
|
{
|
|
|
using (var wait = new AutoResetEvent(false))
|
|
|
{
|
|
|
- _session.RequestWrite(_handle, _serverFilePosition, _writeBuffer, 0, _bufferPosition, wait);
|
|
|
- _serverFilePosition += (ulong) _bufferPosition;
|
|
|
+ _session.RequestWrite(_handle, (ulong) (_position - _bufferPosition), writeBuffer, 0, _bufferPosition, wait);
|
|
|
}
|
|
|
|
|
|
_bufferPosition = 0;
|
|
|
}
|
|
|
|
|
|
// Write the byte into the buffer and advance the posn.
|
|
|
- _writeBuffer[_bufferPosition++] = value;
|
|
|
+ writeBuffer[_bufferPosition++] = value;
|
|
|
++_position;
|
|
|
}
|
|
|
}
|
|
|
@@ -754,6 +788,20 @@ namespace Renci.SshNet.Sftp
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private byte[] GetOrCreateReadBuffer()
|
|
|
+ {
|
|
|
+ if (_readBuffer == null)
|
|
|
+ _readBuffer = new byte[_readBufferSize];
|
|
|
+ return _readBuffer;
|
|
|
+ }
|
|
|
+
|
|
|
+ private byte[] GetOrCreateWriteBuffer()
|
|
|
+ {
|
|
|
+ if (_writeBuffer == null)
|
|
|
+ _writeBuffer = new byte[_writeBufferSize];
|
|
|
+ return _writeBuffer;
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Flushes the read data from the buffer.
|
|
|
/// </summary>
|
|
|
@@ -779,8 +827,7 @@ namespace Renci.SshNet.Sftp
|
|
|
{
|
|
|
using (var wait = new AutoResetEvent(false))
|
|
|
{
|
|
|
- _session.RequestWrite(_handle, _serverFilePosition, _writeBuffer, 0, _bufferPosition, wait);
|
|
|
- _serverFilePosition += (ulong) _bufferPosition;
|
|
|
+ _session.RequestWrite(_handle, (ulong) (_position - _bufferPosition), _writeBuffer, 0, _bufferPosition, wait);
|
|
|
}
|
|
|
|
|
|
_bufferPosition = 0;
|