Browse Source

Add CreateFileReader method on SftpSession in which we use the size of the file to determine the maximum pending reads to allow, and calculate the size of individual chunks.
Add CreateSftpFileReader method to ServiceFactory.
Add nullOnError argument to SftpSession.RequestFStat.

Gert Driesen 8 years ago
parent
commit
3f78ad3002
52 changed files with 202 additions and 82 deletions
  1. 4 1
      src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj
  2. 1 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_LastChunkBeforeEofIsComplete.cs
  3. 1 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_LastChunkBeforeEofIsPartial.cs
  4. 1 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_PreviousChunkIsIncompleteAndEofIsNotReached.cs
  5. 1 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_PreviousChunkIsIncompleteAndEofIsReached.cs
  6. 1 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_ReadAheadEndInvokeException_DiscardsFurtherReadAheads.cs
  7. 1 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_ReadAheadEndInvokeException_PreventsFurtherReadAheads.cs
  8. 1 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_Read_ExceptionInReadAhead.cs
  9. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Closed_FileAccessRead.cs
  10. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Closed_FileAccessReadWrite.cs
  11. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Closed_FileAccessWrite.cs
  12. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Disposed_FileAccessRead.cs
  13. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Disposed_FileAccessReadWrite.cs
  14. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Disposed_FileAccessWrite.cs
  15. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_SessionOpen_FileAccessRead.cs
  16. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_SessionOpen_FileAccessReadWrite.cs
  17. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_SessionOpen_FileAccessWrite.cs
  18. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_Closed_FileAccessRead.cs
  19. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_Closed_FileAccessReadWrite.cs
  20. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_Closed_FileAccessWrite.cs
  21. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_Disposed_FileAccessRead.cs
  22. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_Disposed_FileAccessReadWrite.cs
  23. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_Disposed_FileAccessWrite.cs
  24. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_SessionOpen_FileAccessRead.cs
  25. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_SessionOpen_FileAccessReadWrite.cs
  26. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_SessionOpen_FileAccessWrite.cs
  27. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Close_Closed.cs
  28. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Close_Disposed.cs
  29. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Close_SessionNotOpen.cs
  30. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Close_SessionOpen.cs
  31. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Dispose_Closed.cs
  32. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Dispose_Disposed.cs
  33. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Dispose_SessionNotOpen.cs
  34. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Dispose_SessionOpen.cs
  35. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Finalize_SessionOpen.cs
  36. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_Closed.cs
  37. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_Disposed.cs
  38. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionNotOpen.cs
  39. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessRead.cs
  40. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessReadWrite.cs
  41. 1 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessWrite.cs
  42. 67 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Write_SessionOpen_CountGreatherThanTwoTimesTheWriteBufferSize.cs
  43. 2 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_Connected_RequestRead.cs
  44. 3 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_Connected_RequestStatVfs.cs
  45. 6 0
      src/Renci.SshNet/IServiceFactory.cs
  46. 1 0
      src/Renci.SshNet/Renci.SshNet.csproj
  47. 6 1
      src/Renci.SshNet/ServiceFactory.cs
  48. 12 0
      src/Renci.SshNet/Sftp/ISftpFileReader.cs
  49. 4 1
      src/Renci.SshNet/Sftp/ISftpSession.cs
  50. 3 3
      src/Renci.SshNet/Sftp/SftpFileStream.cs
  51. 32 4
      src/Renci.SshNet/Sftp/SftpSession.cs
  52. 22 21
      src/Renci.SshNet/SftpClient.cs

+ 4 - 1
src/Renci.SshNet.NET35/Renci.SshNet.NET35.csproj

@@ -736,6 +736,9 @@
     <Compile Include="..\Renci.SshNet\Sftp\Flags.cs">
     <Compile Include="..\Renci.SshNet\Sftp\Flags.cs">
       <Link>Sftp\Flags.cs</Link>
       <Link>Sftp\Flags.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\Renci.SshNet\Sftp\ISftpFileReader.cs">
+      <Link>Sftp\ISftpFileReader.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Sftp\ISftpSession.cs">
     <Compile Include="..\Renci.SshNet\Sftp\ISftpSession.cs">
       <Link>Sftp\ISftpSession.cs</Link>
       <Link>Sftp\ISftpSession.cs</Link>
     </Compile>
     </Compile>
@@ -928,7 +931,7 @@
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
   <ProjectExtensions>
     <VisualStudio>
     <VisualStudio>
-      <UserProperties ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
+      <UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="2f5f8c90-0bd1-424f-997c-7bc6280919d1" />
     </VisualStudio>
     </VisualStudio>
   </ProjectExtensions>
   </ProjectExtensions>
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 

+ 1 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_LastChunkBeforeEofIsComplete.cs

@@ -39,7 +39,6 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
         {
             var seq = new MockSequence();
             var seq = new MockSequence();
 
 
-            SftpSessionMock.InSequence(seq).Setup(p => p.RequestFStat(_handle)).Returns(CreateSftpFileAttributes(_fileSize));
             SftpSessionMock.InSequence(seq)
             SftpSessionMock.InSequence(seq)
                             .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                             .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                             .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
                             .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
@@ -70,7 +69,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
         {
             base.Arrange();
             base.Arrange();
 
 
-            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, 15);
+            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 15, _fileSize);
         }
         }
 
 
         protected override void Act()
         protected override void Act()

+ 1 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_LastChunkBeforeEofIsPartial.cs

@@ -39,7 +39,6 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
         {
             var seq = new MockSequence();
             var seq = new MockSequence();
 
 
-            SftpSessionMock.InSequence(seq).Setup(p => p.RequestFStat(_handle)).Returns(CreateSftpFileAttributes(_chunk1.Length + _chunk2.Length));
             SftpSessionMock.InSequence(seq)
             SftpSessionMock.InSequence(seq)
                             .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                             .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                             .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
                             .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
@@ -70,7 +69,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
         {
             base.Arrange();
             base.Arrange();
 
 
-            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, 15);
+            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 15, _fileSize);
         }
         }
 
 
         protected override void Act()
         protected override void Act()

+ 1 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_PreviousChunkIsIncompleteAndEofIsNotReached.cs

@@ -66,7 +66,6 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
         {
             var seq = new MockSequence();
             var seq = new MockSequence();
 
 
-            SftpSessionMock.InSequence(seq).Setup(p => p.RequestFStat(_handle)).Returns(CreateSftpFileAttributes(_fileSize));
             SftpSessionMock.InSequence(seq)
             SftpSessionMock.InSequence(seq)
                             .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                             .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                             .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
                             .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
@@ -133,7 +132,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
         {
             base.Arrange();
             base.Arrange();
 
 
-            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, 3);
+            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 3, _fileSize);
         }
         }
 
 
         protected override void Act()
         protected override void Act()

+ 1 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_PreviousChunkIsIncompleteAndEofIsReached.cs

@@ -48,7 +48,6 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
         {
             var seq = new MockSequence();
             var seq = new MockSequence();
 
 
-            SftpSessionMock.InSequence(seq).Setup(p => p.RequestFStat(_handle)).Returns(CreateSftpFileAttributes(_fileSize));
             SftpSessionMock.InSequence(seq)
             SftpSessionMock.InSequence(seq)
                             .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                             .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                             .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
                             .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
@@ -85,7 +84,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
         {
             base.Arrange();
             base.Arrange();
 
 
-            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, 3);
+            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 3, _fileSize);
         }
         }
 
 
         protected override void Act()
         protected override void Act()

+ 1 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_ReadAheadEndInvokeException_DiscardsFurtherReadAheads.cs

@@ -42,7 +42,6 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
         {
             var seq = new MockSequence();
             var seq = new MockSequence();
 
 
-            SftpSessionMock.InSequence(seq).Setup(p => p.RequestFStat(_handle)).Returns(CreateSftpFileAttributes(_fileSize));
             SftpSessionMock.InSequence(seq)
             SftpSessionMock.InSequence(seq)
                            .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                            .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                            .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
                            .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
@@ -83,7 +82,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
         {
             base.Arrange();
             base.Arrange();
 
 
-            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, 2);
+            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 2, _fileSize);
         }
         }
 
 
         protected override void Act()
         protected override void Act()

+ 1 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_ReadAheadEndInvokeException_PreventsFurtherReadAheads.cs

@@ -44,7 +44,6 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
         {
             var seq = new MockSequence();
             var seq = new MockSequence();
 
 
-            SftpSessionMock.InSequence(seq).Setup(p => p.RequestFStat(_handle)).Returns(CreateSftpFileAttributes(_fileSize));
             SftpSessionMock.InSequence(seq)
             SftpSessionMock.InSequence(seq)
                            .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                            .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                            .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
                            .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
@@ -91,7 +90,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
 
 
             // use a max. read-ahead of 1 to allow us to verify that the next read-ahead is not done
             // use a max. read-ahead of 1 to allow us to verify that the next read-ahead is not done
             // when a read-ahead has failed
             // when a read-ahead has failed
-            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, 1);
+            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 1, _fileSize);
         }
         }
 
 
         protected override void Act()
         protected override void Act()

+ 1 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_Read_ExceptionInReadAhead.cs

@@ -43,7 +43,6 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
         {
             var seq = new MockSequence();
             var seq = new MockSequence();
 
 
-            SftpSessionMock.InSequence(seq).Setup(p => p.RequestFStat(_handle)).Returns(CreateSftpFileAttributes(_fileSize));
             SftpSessionMock.InSequence(seq)
             SftpSessionMock.InSequence(seq)
                            .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                            .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                            .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
                            .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
@@ -76,7 +75,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
         {
             base.Arrange();
             base.Arrange();
 
 
-            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, 3);
+            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 3, _fileSize);
         }
         }
 
 
         protected override void Act()
         protected override void Act()

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Closed_FileAccessRead.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Closed_FileAccessReadWrite.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Closed_FileAccessWrite.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Disposed_FileAccessRead.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Disposed_FileAccessReadWrite.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Disposed_FileAccessWrite.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_SessionOpen_FileAccessRead.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_SessionOpen_FileAccessReadWrite.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_SessionOpen_FileAccessWrite.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_Closed_FileAccessRead.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_Closed_FileAccessReadWrite.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_Closed_FileAccessWrite.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_Disposed_FileAccessRead.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_Disposed_FileAccessReadWrite.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_Disposed_FileAccessWrite.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_SessionOpen_FileAccessRead.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_SessionOpen_FileAccessReadWrite.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanWrite_SessionOpen_FileAccessWrite.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Close_Closed.cs

@@ -42,7 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Close_Disposed.cs

@@ -42,7 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Close_SessionNotOpen.cs

@@ -42,7 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Close_SessionOpen.cs

@@ -42,7 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Dispose_Closed.cs

@@ -42,7 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Dispose_Disposed.cs

@@ -42,7 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Dispose_SessionNotOpen.cs

@@ -42,7 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Dispose_SessionOpen.cs

@@ -42,7 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Finalize_SessionOpen.cs

@@ -42,7 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_Closed.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_Disposed.cs

@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(sequence)
             _sftpSessionMock.InSequence(sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionNotOpen.cs

@@ -46,7 +46,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(_sequence)
             _sftpSessionMock.InSequence(_sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(_sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(_sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(_sequence)
             _sftpSessionMock.InSequence(_sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessRead.cs

@@ -46,7 +46,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(_sequence)
             _sftpSessionMock.InSequence(_sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(_sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(_sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(_sequence)
             _sftpSessionMock.InSequence(_sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessReadWrite.cs

@@ -46,7 +46,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(_sequence)
             _sftpSessionMock.InSequence(_sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(_sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(_sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(_sequence)
             _sftpSessionMock.InSequence(_sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessWrite.cs

@@ -46,7 +46,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(_sequence)
             _sftpSessionMock.InSequence(_sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(_sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(_sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(_sequence)
             _sftpSessionMock.InSequence(_sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);

+ 67 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Write_SessionOpen_CountGreatherThanTwoTimesTheWriteBufferSize.cs

@@ -80,7 +80,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.InSequence(_sequence)
             _sftpSessionMock.InSequence(_sequence)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .Returns(_handle);
                 .Returns(_handle);
-            _sftpSessionMock.InSequence(_sequence).Setup(p => p.RequestFStat(_handle)).Returns(_fileAttributes);
+            _sftpSessionMock.InSequence(_sequence).Setup(p => p.RequestFStat(_handle, false)).Returns(_fileAttributes);
             _sftpSessionMock.InSequence(_sequence)
             _sftpSessionMock.InSequence(_sequence)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .Returns(_readBufferSize);
                 .Returns(_readBufferSize);
@@ -134,7 +134,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
                             .Setup(p => p.RequestWrite(_handle, _expectedWrittenByteCount, It.IsAny<byte[]>(), 0, _expectedBufferedByteCount, It.IsAny<AutoResetEvent>(), null))
                             .Setup(p => p.RequestWrite(_handle, _expectedWrittenByteCount, It.IsAny<byte[]>(), 0, _expectedBufferedByteCount, It.IsAny<AutoResetEvent>(), null))
                             .Callback<byte[], ulong, byte[], int, int, AutoResetEvent, Action<SftpStatusResponse>>((handle, serverFileOffset, data, offset, length, wait, writeCompleted) => actualFlushedData = data.Take(offset, length));
                             .Callback<byte[], ulong, byte[], int, int, AutoResetEvent, Action<SftpStatusResponse>>((handle, serverFileOffset, data, offset, length, wait, writeCompleted) => actualFlushedData = data.Take(offset, length));
             _sftpSessionMock.InSequence(_sequence)
             _sftpSessionMock.InSequence(_sequence)
-                            .Setup(p => p.RequestFStat(_handle))
+                            .Setup(p => p.RequestFStat(_handle, true))
                             .Returns(lengthFileAttributes);
                             .Returns(lengthFileAttributes);
 
 
             Assert.AreEqual(lengthFileAttributes.Size, _sftpFileStream.Length);
             Assert.AreEqual(lengthFileAttributes.Size, _sftpFileStream.Length);
@@ -143,6 +143,71 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _sftpSessionMock.Verify(p => p.RequestWrite(_handle, _expectedWrittenByteCount, It.IsAny<byte[]>(), 0, _expectedBufferedByteCount, It.IsAny<AutoResetEvent>(), null), Times.Once);
             _sftpSessionMock.Verify(p => p.RequestWrite(_handle, _expectedWrittenByteCount, It.IsAny<byte[]>(), 0, _expectedBufferedByteCount, It.IsAny<AutoResetEvent>(), null), Times.Once);
         }
         }
 
 
+        [TestMethod]
+        public void LengthShouldThrowIOExceptionIfRequestFStatReturnsNull()
+        {
+            const SftpFileAttributes lengthFileAttributes = null;
+            byte[] actualFlushedData = null;
+
+            _sftpSessionMock.InSequence(_sequence)
+                            .Setup(p => p.IsOpen)
+                            .Returns(true);
+            _sftpSessionMock.InSequence(_sequence)
+                            .Setup(p => p.RequestWrite(_handle, _expectedWrittenByteCount, It.IsAny<byte[]>(), 0, _expectedBufferedByteCount, It.IsAny<AutoResetEvent>(), null))
+                            .Callback<byte[], ulong, byte[], int, int, AutoResetEvent, Action<SftpStatusResponse>>((handle, serverFileOffset, data, offset, length, wait, writeCompleted) => actualFlushedData = data.Take(offset, length));
+            _sftpSessionMock.InSequence(_sequence)
+                            .Setup(p => p.RequestFStat(_handle, true))
+                            .Returns(lengthFileAttributes);
+
+            try
+            {
+                var length = _sftpFileStream.Length;
+                Assert.Fail();
+            }
+            catch (IOException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("Seek operation failed.", ex.Message);
+            }
+
+            Assert.IsTrue(actualFlushedData.IsEqualTo(_expectedBufferedBytes));
+
+            _sftpSessionMock.Verify(p => p.RequestWrite(_handle, _expectedWrittenByteCount, It.IsAny<byte[]>(), 0, _expectedBufferedByteCount, It.IsAny<AutoResetEvent>(), null), Times.Once);
+        }
+
+        [TestMethod]
+        public void LengthShouldThrowIOExceptionIfSizeIsMinusOne()
+        {
+            var lengthFileAttributes = new SftpFileAttributes(DateTime.Now, DateTime.Now, -1, _random.Next(), _random.Next(), (uint)_random.Next(0, int.MaxValue), null);
+            byte[] actualFlushedData = null;
+
+            _sftpSessionMock.InSequence(_sequence)
+                            .Setup(p => p.IsOpen)
+                            .Returns(true);
+            _sftpSessionMock.InSequence(_sequence)
+                            .Setup(p => p.RequestWrite(_handle, _expectedWrittenByteCount, It.IsAny<byte[]>(), 0, _expectedBufferedByteCount, It.IsAny<AutoResetEvent>(), null))
+                            .Callback<byte[], ulong, byte[], int, int, AutoResetEvent, Action<SftpStatusResponse>>((handle, serverFileOffset, data, offset, length, wait, writeCompleted) => actualFlushedData = data.Take(offset, length));
+            _sftpSessionMock.InSequence(_sequence)
+                            .Setup(p => p.RequestFStat(_handle, true))
+                            .Returns(lengthFileAttributes);
+
+            try
+            {
+                var length = _sftpFileStream.Length;
+                Assert.Fail();
+            }
+            catch (IOException ex)
+            {
+                Assert.IsNull(ex.InnerException);
+                Assert.AreEqual("Seek operation failed.", ex.Message);
+            }
+
+            Assert.IsTrue(actualFlushedData.IsEqualTo(_expectedBufferedBytes));
+
+            _sftpSessionMock.Verify(p => p.RequestWrite(_handle, _expectedWrittenByteCount, It.IsAny<byte[]>(), 0, _expectedBufferedByteCount, It.IsAny<AutoResetEvent>(), null), Times.Once);
+        }
+
+
         [TestMethod]
         [TestMethod]
         public void DisposeShouldFlushBufferAndCloseRequest()
         public void DisposeShouldFlushBufferAndCloseRequest()
         {
         {

+ 2 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_Connected_RequestRead.cs

@@ -14,6 +14,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
     public class SftpSessionTest_Connected_RequestRead
     public class SftpSessionTest_Connected_RequestRead
     {
     {
         private Mock<ISession> _sessionMock;
         private Mock<ISession> _sessionMock;
+        private Mock<IServiceFactory> _serviceFactoryMock;
         private Mock<IChannelSession> _channelSessionMock;
         private Mock<IChannelSession> _channelSessionMock;
         private SftpSession _sftpSession;
         private SftpSession _sftpSession;
         private TimeSpan _operationTimeout;
         private TimeSpan _operationTimeout;
@@ -82,7 +83,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
                     }
                     }
                 );
                 );
 
 
-            _sftpSession = new SftpSession(_sessionMock.Object, _operationTimeout, _encoding);
+            _sftpSession = new SftpSession(_sessionMock.Object, _operationTimeout, _encoding, _serviceFactoryMock.Object);
             _sftpSession.Connect();
             _sftpSession.Connect();
         }
         }
 
 

+ 3 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_Connected_RequestStatVfs.cs

@@ -14,6 +14,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
     {
     {
         private Mock<ISession> _sessionMock;
         private Mock<ISession> _sessionMock;
         private Mock<IChannelSession> _channelSessionMock;
         private Mock<IChannelSession> _channelSessionMock;
+        private Mock<IServiceFactory> _serviceFactoryMock;
         private SftpSession _sftpSession;
         private SftpSession _sftpSession;
         private TimeSpan _operationTimeout;
         private TimeSpan _operationTimeout;
         private SftpFileSytemInformation _actual;
         private SftpFileSytemInformation _actual;
@@ -39,6 +40,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
 
 
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
             _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
             _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
 
 
             var sequence = new MockSequence();
             var sequence = new MockSequence();
 
 
@@ -82,7 +84,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
                     }
                     }
                 );
                 );
 
 
-            _sftpSession = new SftpSession(_sessionMock.Object, _operationTimeout, _encoding);
+            _sftpSession = new SftpSession(_sessionMock.Object, _operationTimeout, _encoding, _serviceFactoryMock.Object);
             _sftpSession.Connect();
             _sftpSession.Connect();
         }
         }
 
 

+ 6 - 0
src/Renci.SshNet/IServiceFactory.cs

@@ -57,5 +57,11 @@ namespace Renci.SshNet
         /// <exception cref="ArgumentNullException"><paramref name="serverAlgorithms"/> is <c>null</c>.</exception>
         /// <exception cref="ArgumentNullException"><paramref name="serverAlgorithms"/> is <c>null</c>.</exception>
         /// <exception cref="SshConnectionException">No key exchange algorithm is supported by both client and server.</exception>
         /// <exception cref="SshConnectionException">No key exchange algorithm is supported by both client and server.</exception>
         IKeyExchange CreateKeyExchange(IDictionary<string, Type> clientAlgorithms, string[] serverAlgorithms);
         IKeyExchange CreateKeyExchange(IDictionary<string, Type> clientAlgorithms, string[] serverAlgorithms);
+
+        ISftpFileReader CreateSftpFileReader(byte[] handle,
+                                             ISftpSession sftpSession,
+                                             uint chunkSize,
+                                             int maxPendingReads,
+                                             long? fileSize);
     }
     }
 }
 }

+ 1 - 0
src/Renci.SshNet/Renci.SshNet.csproj

@@ -170,6 +170,7 @@
     <Compile Include="Security\KeyExchangeDiffieHellmanGroupExchangeShaBase.cs" />
     <Compile Include="Security\KeyExchangeDiffieHellmanGroupExchangeShaBase.cs" />
     <Compile Include="ServiceFactory.cs" />
     <Compile Include="ServiceFactory.cs" />
     <Compile Include="ServiceFactory.NET.cs" />
     <Compile Include="ServiceFactory.NET.cs" />
+    <Compile Include="Sftp\ISftpFileReader.cs" />
     <Compile Include="Sftp\ISftpSession.cs" />
     <Compile Include="Sftp\ISftpSession.cs" />
     <Compile Include="Common\SshDataStream.cs" />
     <Compile Include="Common\SshDataStream.cs" />
     <Compile Include="ExpectAsyncResult.cs" />
     <Compile Include="ExpectAsyncResult.cs" />

+ 6 - 1
src/Renci.SshNet/ServiceFactory.cs

@@ -50,7 +50,7 @@ namespace Renci.SshNet
         /// </returns>
         /// </returns>
         public ISftpSession CreateSftpSession(ISession session, TimeSpan operationTimeout, Encoding encoding)
         public ISftpSession CreateSftpSession(ISession session, TimeSpan operationTimeout, Encoding encoding)
         {
         {
-            return new SftpSession(session, operationTimeout, encoding);
+            return new SftpSession(session, operationTimeout, encoding, this);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -96,5 +96,10 @@ namespace Renci.SshNet
 
 
             return keyExchangeAlgorithmType.CreateInstance<IKeyExchange>();
             return keyExchangeAlgorithmType.CreateInstance<IKeyExchange>();
         }
         }
+
+        public ISftpFileReader CreateSftpFileReader(byte[] handle, ISftpSession sftpSession, uint chunkSize, int maxPendingReads, long? fileSize)
+        {
+            return new SftpFileReader(handle, sftpSession, chunkSize, maxPendingReads, fileSize);
+        }
     }
     }
 }
 }

+ 12 - 0
src/Renci.SshNet/Sftp/ISftpFileReader.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Renci.SshNet.Sftp
+{
+    internal interface ISftpFileReader : IDisposable
+    {
+        byte[] Read();
+    }
+}

+ 4 - 1
src/Renci.SshNet/Sftp/ISftpSession.cs

@@ -42,10 +42,11 @@ namespace Renci.SshNet.Sftp
         /// Performs SSH_FXP_FSTAT request.
         /// Performs SSH_FXP_FSTAT request.
         /// </summary>
         /// </summary>
         /// <param name="handle">The handle.</param>
         /// <param name="handle">The handle.</param>
+        /// <param name="nullOnError">if set to <c>true</c> returns <c>null</c> instead of throwing an exception.</param>
         /// <returns>
         /// <returns>
         /// File attributes
         /// File attributes
         /// </returns>
         /// </returns>
-        SftpFileAttributes RequestFStat(byte[] handle);
+        SftpFileAttributes RequestFStat(byte[] handle, bool nullOnError);
 
 
         /// <summary>
         /// <summary>
         /// Performs SSH_FXP_LSTAT request.
         /// Performs SSH_FXP_LSTAT request.
@@ -222,5 +223,7 @@ namespace Renci.SshNet.Sftp
         /// Currently, we do not take the remote window size into account.
         /// Currently, we do not take the remote window size into account.
         /// </remarks>
         /// </remarks>
         uint CalculateOptimalWriteLength(uint bufferSize, byte[] handle);
         uint CalculateOptimalWriteLength(uint bufferSize, byte[] handle);
+
+        ISftpFileReader CreateFileReader(string fileName, uint bufferSize);
     }
     }
 }
 }

+ 3 - 3
src/Renci.SshNet/Sftp/SftpFileStream.cs

@@ -102,7 +102,7 @@ namespace Renci.SshNet.Sftp
                     }
                     }
 
 
                     //  Update file attributes
                     //  Update file attributes
-                    _attributes = _session.RequestFStat(_handle);
+                    _attributes = _session.RequestFStat(_handle, true);
 
 
                     if (_attributes != null && _attributes.Size > -1)
                     if (_attributes != null && _attributes.Size > -1)
                     {
                     {
@@ -269,7 +269,7 @@ namespace Renci.SshNet.Sftp
             if (_handle == null)
             if (_handle == null)
                 _handle = _session.RequestOpen(path, flags);
                 _handle = _session.RequestOpen(path, flags);
 
 
-            _attributes = _session.RequestFStat(_handle);
+            _attributes = _session.RequestFStat(_handle, false);
 
 
             // instead of using the specified buffer size as is, we use it to calculate a buffer size
             // instead of using the specified buffer size as is, we use it to calculate a buffer size
             // that ensures we always receive or send the max. number of bytes in a single SSH_FXP_READ
             // that ensures we always receive or send the max. number of bytes in a single SSH_FXP_READ
@@ -475,7 +475,7 @@ namespace Renci.SshNet.Sftp
                     return _position;
                     return _position;
                 }
                 }
 
 
-                _attributes = _session.RequestFStat(_handle);
+                _attributes = _session.RequestFStat(_handle, false);
 
 
                 // The behaviour depends upon the read/write mode.
                 // The behaviour depends upon the read/write mode.
                 if (_bufferOwnedByWrite)
                 if (_bufferOwnedByWrite)

+ 32 - 4
src/Renci.SshNet/Sftp/SftpSession.cs

@@ -17,6 +17,7 @@ namespace Renci.SshNet.Sftp
         private readonly Dictionary<uint, SftpRequest> _requests = new Dictionary<uint, SftpRequest>();
         private readonly Dictionary<uint, SftpRequest> _requests = new Dictionary<uint, SftpRequest>();
         //FIXME: obtain from SftpClient!
         //FIXME: obtain from SftpClient!
         private readonly List<byte> _data = new List<byte>(32 * 1024);
         private readonly List<byte> _data = new List<byte>(32 * 1024);
+        private readonly IServiceFactory _serviceFactory;
         private EventWaitHandle _sftpVersionConfirmed = new AutoResetEvent(false);
         private EventWaitHandle _sftpVersionConfirmed = new AutoResetEvent(false);
         private IDictionary<string, string> _supportedExtensions;
         private IDictionary<string, string> _supportedExtensions;
 
 
@@ -37,6 +38,7 @@ namespace Renci.SshNet.Sftp
         public uint ProtocolVersion { get; private set; }
         public uint ProtocolVersion { get; private set; }
 
 
         private long _requestId;
         private long _requestId;
+
         /// <summary>
         /// <summary>
         /// Gets the next request id for sftp session.
         /// Gets the next request id for sftp session.
         /// </summary>
         /// </summary>
@@ -48,9 +50,10 @@ namespace Renci.SshNet.Sftp
             }
             }
         }
         }
 
 
-        public SftpSession(ISession session, TimeSpan operationTimeout, Encoding encoding)
+        public SftpSession(ISession session, TimeSpan operationTimeout, Encoding encoding, IServiceFactory serviceFactory)
             : base(session, "sftp", operationTimeout, encoding)
             : base(session, "sftp", operationTimeout, encoding)
         {
         {
+            _serviceFactory = serviceFactory;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -127,6 +130,30 @@ namespace Renci.SshNet.Sftp
             return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", canonizedPath, slash, pathParts[pathParts.Length - 1]);
             return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", canonizedPath, slash, pathParts[pathParts.Length - 1]);
         }
         }
 
 
+        public ISftpFileReader CreateFileReader(string fileName, uint bufferSize)
+        {
+            var handle = RequestOpen(fileName, Flags.Read);
+
+            long? fileSize;
+            int maxPendingReads;
+
+            var chunkSize = CalculateOptimalReadLength(bufferSize);
+
+            var fileAttributes = RequestFStat(handle, true);
+            if (fileAttributes == null)
+            {
+                fileSize = null;
+                maxPendingReads = 5;
+            }
+            else
+            {
+                fileSize = fileAttributes.Size;
+                maxPendingReads = Math.Min(10, (int)Math.Ceiling((double)fileAttributes.Size / chunkSize) + 1);
+            }
+
+            return _serviceFactory.CreateSftpFileReader(handle, this, chunkSize, maxPendingReads, fileSize);
+        }
+
         internal string GetFullRemotePath(string path)
         internal string GetFullRemotePath(string path)
         {
         {
             var fullPath = path;
             var fullPath = path;
@@ -590,10 +617,11 @@ namespace Renci.SshNet.Sftp
         /// Performs SSH_FXP_FSTAT request.
         /// Performs SSH_FXP_FSTAT request.
         /// </summary>
         /// </summary>
         /// <param name="handle">The handle.</param>
         /// <param name="handle">The handle.</param>
+        /// <param name="nullOnError">if set to <c>true</c> returns <c>null</c> instead of throwing an exception.</param>
         /// <returns>
         /// <returns>
         /// File attributes
         /// File attributes
         /// </returns>
         /// </returns>
-        public SftpFileAttributes RequestFStat(byte[] handle)
+        public SftpFileAttributes RequestFStat(byte[] handle, bool nullOnError)
         {
         {
             SshException exception = null;
             SshException exception = null;
             SftpFileAttributes attributes = null;
             SftpFileAttributes attributes = null;
@@ -617,7 +645,7 @@ namespace Renci.SshNet.Sftp
                 WaitOnHandle(wait, OperationTimeout);
                 WaitOnHandle(wait, OperationTimeout);
             }
             }
 
 
-            if (exception != null)
+            if (exception != null && !nullOnError)
             {
             {
                 throw exception;
                 throw exception;
             }
             }
@@ -687,7 +715,7 @@ namespace Renci.SshNet.Sftp
         /// Performs SSH_FXP_OPENDIR request
         /// Performs SSH_FXP_OPENDIR request
         /// </summary>
         /// </summary>
         /// <param name="path">The path.</param>
         /// <param name="path">The path.</param>
-        /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
+        /// <param name="nullOnError">if set to <c>true</c> returns <c>null</c> instead of throwing an exception.</param>
         /// <returns>File handle.</returns>
         /// <returns>File handle.</returns>
         public byte[] RequestOpenDir(string path, bool nullOnError = false)
         public byte[] RequestOpenDir(string path, bool nullOnError = false)
         {
         {

+ 22 - 21
src/Renci.SshNet/SftpClient.cs

@@ -1995,35 +1995,36 @@ namespace Renci.SshNet
 
 
             var fullPath = _sftpSession.GetCanonicalPath(path);
             var fullPath = _sftpSession.GetCanonicalPath(path);
 
 
-            var handle = _sftpSession.RequestOpen(fullPath, Flags.Read);
-            
-            // TODO close handle in case of exception
-            // TODO decide whether to move opening (and closing) of handle to SftpFileReader
+            using (var fileReader = _sftpSession.CreateFileReader(fullPath, _bufferSize))
+            {
+                var totalBytesRead = 0UL;
 
 
-            var fileReader = new SftpFileReader(handle, _sftpSession, 15);
-            var totalBytesRead = 0UL;
+                while (true)
+                {
+                    // TODO: cancel read ahead when download is canceled by user
 
 
-            while (true)
-            {
-                // TODO: cancel read ahead when download is canceled by user
+                    //  Cancel download
+                    if (asyncResult != null && asyncResult.IsDownloadCanceled)
+                        break;
 
 
-                //  Cancel download
-                if (asyncResult != null && asyncResult.IsDownloadCanceled)
-                    break;
+                    var data = fileReader.Read();
+                    if (data.Length == 0)
+                        break;
 
 
-                var data = fileReader.Read();
-                if (data.Length == 0)
-                    break;
+                    output.Write(data, 0, data.Length);
 
 
-                output.Write(data, 0, data.Length);
+                    totalBytesRead += (ulong)data.Length;
 
 
-                totalBytesRead += (ulong) data.Length;
+                    if (downloadCallback != null)
+                    {
+                        // copy offset to ensure it's not modified between now and execution of callback
+                        var downloadOffset = totalBytesRead;
 
 
-                if (downloadCallback != null)
-                    downloadCallback(totalBytesRead);
+                        //  Execute callback on different thread
+                        ThreadAbstraction.ExecuteThread(() => { downloadCallback(downloadOffset); });
+                    }
+                }
             }
             }
-
-            _sftpSession.RequestClose(handle);
         }
         }
 
 
         /// <summary>
         /// <summary>