浏览代码

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 年之前
父节点
当前提交
3f78ad3002
共有 52 个文件被更改,包括 202 次插入82 次删除
  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">
       <Link>Sftp\Flags.cs</Link>
     </Compile>
+    <Compile Include="..\Renci.SshNet\Sftp\ISftpFileReader.cs">
+      <Link>Sftp\ISftpFileReader.cs</Link>
+    </Compile>
     <Compile Include="..\Renci.SshNet\Sftp\ISftpSession.cs">
       <Link>Sftp\ISftpSession.cs</Link>
     </Compile>
@@ -928,7 +931,7 @@
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
     <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>
   </ProjectExtensions>
   <!-- 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();
 
-            SftpSessionMock.InSequence(seq).Setup(p => p.RequestFStat(_handle)).Returns(CreateSftpFileAttributes(_fileSize));
             SftpSessionMock.InSequence(seq)
                             .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                             .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
@@ -70,7 +69,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
             base.Arrange();
 
-            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, 15);
+            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 15, _fileSize);
         }
 
         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();
 
-            SftpSessionMock.InSequence(seq).Setup(p => p.RequestFStat(_handle)).Returns(CreateSftpFileAttributes(_chunk1.Length + _chunk2.Length));
             SftpSessionMock.InSequence(seq)
                             .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                             .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
@@ -70,7 +69,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
             base.Arrange();
 
-            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, 15);
+            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 15, _fileSize);
         }
 
         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();
 
-            SftpSessionMock.InSequence(seq).Setup(p => p.RequestFStat(_handle)).Returns(CreateSftpFileAttributes(_fileSize));
             SftpSessionMock.InSequence(seq)
                             .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                             .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
@@ -133,7 +132,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
             base.Arrange();
 
-            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, 3);
+            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 3, _fileSize);
         }
 
         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();
 
-            SftpSessionMock.InSequence(seq).Setup(p => p.RequestFStat(_handle)).Returns(CreateSftpFileAttributes(_fileSize));
             SftpSessionMock.InSequence(seq)
                             .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                             .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
@@ -85,7 +84,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
             base.Arrange();
 
-            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, 3);
+            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 3, _fileSize);
         }
 
         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();
 
-            SftpSessionMock.InSequence(seq).Setup(p => p.RequestFStat(_handle)).Returns(CreateSftpFileAttributes(_fileSize));
             SftpSessionMock.InSequence(seq)
                            .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                            .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
@@ -83,7 +82,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
             base.Arrange();
 
-            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, 2);
+            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 2, _fileSize);
         }
 
         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();
 
-            SftpSessionMock.InSequence(seq).Setup(p => p.RequestFStat(_handle)).Returns(CreateSftpFileAttributes(_fileSize));
             SftpSessionMock.InSequence(seq)
                            .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                            .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
             // 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()

+ 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();
 
-            SftpSessionMock.InSequence(seq).Setup(p => p.RequestFStat(_handle)).Returns(CreateSftpFileAttributes(_fileSize));
             SftpSessionMock.InSequence(seq)
                            .Setup(p => p.BeginRead(_handle, 0, ChunkLength, It.IsNotNull<AsyncCallback>(), It.IsAny<BufferedRead>()))
                            .Callback<byte[], ulong, uint, AsyncCallback, object>((handle, offset, length, callback, state) =>
@@ -76,7 +75,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         {
             base.Arrange();
 
-            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, 3);
+            _reader = new SftpFileReader(_handle, SftpSessionMock.Object, ChunkLength, 3, _fileSize);
         }
 
         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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Read | Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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)
                 .Setup(p => p.RequestOpen(_path, Flags.Write | Flags.Truncate, true))
                 .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)
                 .Setup(p => p.CalculateOptimalReadLength(_bufferSize))
                 .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))
                             .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))
+                            .Setup(p => p.RequestFStat(_handle, true))
                             .Returns(lengthFileAttributes);
 
             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);
         }
 
+        [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]
         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
     {
         private Mock<ISession> _sessionMock;
+        private Mock<IServiceFactory> _serviceFactoryMock;
         private Mock<IChannelSession> _channelSessionMock;
         private SftpSession _sftpSession;
         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();
         }
 

+ 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<IChannelSession> _channelSessionMock;
+        private Mock<IServiceFactory> _serviceFactoryMock;
         private SftpSession _sftpSession;
         private TimeSpan _operationTimeout;
         private SftpFileSytemInformation _actual;
@@ -39,6 +40,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
 
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
             _channelSessionMock = new Mock<IChannelSession>(MockBehavior.Strict);
+            _serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
 
             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();
         }
 

+ 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="SshConnectionException">No key exchange algorithm is supported by both client and server.</exception>
         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="ServiceFactory.cs" />
     <Compile Include="ServiceFactory.NET.cs" />
+    <Compile Include="Sftp\ISftpFileReader.cs" />
     <Compile Include="Sftp\ISftpSession.cs" />
     <Compile Include="Common\SshDataStream.cs" />
     <Compile Include="ExpectAsyncResult.cs" />

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

@@ -50,7 +50,7 @@ namespace Renci.SshNet
         /// </returns>
         public ISftpSession CreateSftpSession(ISession session, TimeSpan operationTimeout, Encoding encoding)
         {
-            return new SftpSession(session, operationTimeout, encoding);
+            return new SftpSession(session, operationTimeout, encoding, this);
         }
 
         /// <summary>
@@ -96,5 +96,10 @@ namespace Renci.SshNet
 
             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.
         /// </summary>
         /// <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>
         /// File attributes
         /// </returns>
-        SftpFileAttributes RequestFStat(byte[] handle);
+        SftpFileAttributes RequestFStat(byte[] handle, bool nullOnError);
 
         /// <summary>
         /// Performs SSH_FXP_LSTAT request.
@@ -222,5 +223,7 @@ namespace Renci.SshNet.Sftp
         /// Currently, we do not take the remote window size into account.
         /// </remarks>
         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
-                    _attributes = _session.RequestFStat(_handle);
+                    _attributes = _session.RequestFStat(_handle, true);
 
                     if (_attributes != null && _attributes.Size > -1)
                     {
@@ -269,7 +269,7 @@ namespace Renci.SshNet.Sftp
             if (_handle == null)
                 _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
             // 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;
                 }
 
-                _attributes = _session.RequestFStat(_handle);
+                _attributes = _session.RequestFStat(_handle, false);
 
                 // The behaviour depends upon the read/write mode.
                 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>();
         //FIXME: obtain from SftpClient!
         private readonly List<byte> _data = new List<byte>(32 * 1024);
+        private readonly IServiceFactory _serviceFactory;
         private EventWaitHandle _sftpVersionConfirmed = new AutoResetEvent(false);
         private IDictionary<string, string> _supportedExtensions;
 
@@ -37,6 +38,7 @@ namespace Renci.SshNet.Sftp
         public uint ProtocolVersion { get; private set; }
 
         private long _requestId;
+
         /// <summary>
         /// Gets the next request id for sftp session.
         /// </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)
         {
+            _serviceFactory = serviceFactory;
         }
 
         /// <summary>
@@ -127,6 +130,30 @@ namespace Renci.SshNet.Sftp
             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)
         {
             var fullPath = path;
@@ -590,10 +617,11 @@ namespace Renci.SshNet.Sftp
         /// Performs SSH_FXP_FSTAT request.
         /// </summary>
         /// <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>
         /// File attributes
         /// </returns>
-        public SftpFileAttributes RequestFStat(byte[] handle)
+        public SftpFileAttributes RequestFStat(byte[] handle, bool nullOnError)
         {
             SshException exception = null;
             SftpFileAttributes attributes = null;
@@ -617,7 +645,7 @@ namespace Renci.SshNet.Sftp
                 WaitOnHandle(wait, OperationTimeout);
             }
 
-            if (exception != null)
+            if (exception != null && !nullOnError)
             {
                 throw exception;
             }
@@ -687,7 +715,7 @@ namespace Renci.SshNet.Sftp
         /// Performs SSH_FXP_OPENDIR request
         /// </summary>
         /// <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>
         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 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>