mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-11 13:39:09 +00:00
Attempt to run tests against .NET 3.5 and 4.0.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
@@ -12,29 +12,33 @@
|
||||
<AssemblyName>Renci.SshNet</AssemblyName>
|
||||
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
|
||||
<DefineConstants>TRACE;DEBUG;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_SOCKET_SELECT;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<DocumentationFile>bin\Debug\Renci.SshNet.xml</DocumentationFile>
|
||||
<LangVersion>5</LangVersion>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
|
||||
<DefineConstants>TRACE;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_SOCKET_SELECT;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>bin\Release\Renci.SshNet.xml</DocumentationFile>
|
||||
<NoWarn>
|
||||
</NoWarn>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
@@ -861,6 +865,9 @@
|
||||
<Compile Include="..\Renci.SshNet\Sftp\SftpFileAttributes.cs">
|
||||
<Link>Sftp\SftpFileAttributes.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Renci.SshNet\Sftp\SftpFileReader.cs">
|
||||
<Link>Sftp\SftpFileReader.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Renci.SshNet\Sftp\SftpFileStream.cs">
|
||||
<Link>Sftp\SftpFileStream.cs</Link>
|
||||
</Compile>
|
||||
@@ -876,6 +883,9 @@
|
||||
<Compile Include="..\Renci.SshNet\Sftp\SftpMessageTypes.cs">
|
||||
<Link>Sftp\SftpMessageTypes.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Renci.SshNet\Sftp\SftpReadAsyncResult.cs">
|
||||
<Link>Sftp\SftpReadAsyncResult.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Renci.SshNet\Sftp\SftpSession.cs">
|
||||
<Link>Sftp\SftpSession.cs</Link>
|
||||
</Compile>
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
<Reference Include="Moq">
|
||||
<HintPath>..\..\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll</HintPath>
|
||||
<Reference Include="Moq, Version=4.2.1409.1722, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\..\..\Users\Gert Driesen\Dropbox\Privé\Projects\SShNetTests\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core">
|
||||
|
||||
@@ -29,10 +29,12 @@ namespace Renci.SshNet.Common
|
||||
private Exception _exception;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether EndInvoke has been called on the current AsyncResult.
|
||||
/// Gets or sets a value indicating whether <see cref="EndInvoke()"/> has been called on the current
|
||||
/// <see cref="AsyncResult"/>.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if <see cref="EndInvoke()"/> has been called on the current <see cref="AsyncResult"/>; otherwise, <c>false</c>.
|
||||
/// <c>true</c> if <see cref="EndInvoke()"/> has been called on the current <see cref="AsyncResult"/>;
|
||||
/// otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool EndInvokeCalled { get; private set; }
|
||||
|
||||
@@ -75,7 +77,7 @@ namespace Renci.SshNet.Common
|
||||
/// <summary>
|
||||
/// Waits until the asynchronous operation completes, and then returns.
|
||||
/// </summary>
|
||||
public void EndInvoke()
|
||||
internal void EndInvoke()
|
||||
{
|
||||
// This method assumes that only 1 thread calls EndInvoke for this object
|
||||
if (!IsCompleted)
|
||||
@@ -93,6 +95,28 @@ namespace Renci.SshNet.Common
|
||||
throw _exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits until the asynchronous operation completes, and then returns.
|
||||
/// </summary>
|
||||
internal void EndInvoke(TimeSpan timeout)
|
||||
{
|
||||
// This method assumes that only 1 thread calls EndInvoke for this object
|
||||
if (!IsCompleted)
|
||||
{
|
||||
// If the operation isn't done, wait for it
|
||||
var completed = AsyncWaitHandle.WaitOne(timeout);
|
||||
_asyncWaitHandle = null; // Allow early GC
|
||||
AsyncWaitHandle.Dispose();
|
||||
|
||||
}
|
||||
|
||||
EndInvokeCalled = true;
|
||||
|
||||
// Operation is done: if an exception occurred, throw it
|
||||
if (_exception != null)
|
||||
throw _exception;
|
||||
}
|
||||
|
||||
#region Implementation of IAsyncResult
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -64,7 +64,6 @@ namespace Renci.SshNet.Common
|
||||
/// </summary>
|
||||
public void Wait()
|
||||
{
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
while (_currentCount < 1)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
|
||||
<DefineConstants>TRACE;DEBUG;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>bin\Debug\Renci.SshNet.xml</DocumentationFile>
|
||||
@@ -29,13 +29,14 @@
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
|
||||
<DefineConstants>FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SETSOCKETOPTION;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>bin\Release\Renci.SshNet.xml</DocumentationFile>
|
||||
<NoWarn>
|
||||
</NoWarn>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<LangVersion>5</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
@@ -416,6 +417,7 @@
|
||||
<Compile Include="Sftp\SftpDownloadAsyncResult.cs" />
|
||||
<Compile Include="Sftp\SftpFile.cs" />
|
||||
<Compile Include="Sftp\SftpFileAttributes.cs" />
|
||||
<Compile Include="Sftp\SftpFileReader.cs" />
|
||||
<Compile Include="Sftp\SftpFileStream.cs" />
|
||||
<Compile Include="Sftp\SftpFileSystemInformation.cs">
|
||||
<SubType>Code</SubType>
|
||||
@@ -425,6 +427,7 @@
|
||||
<Compile Include="Sftp\SftpMessageTypes.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Sftp\SftpReadAsyncResult.cs" />
|
||||
<Compile Include="Sftp\SftpSession.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
|
||||
@@ -1815,14 +1815,16 @@ namespace Renci.SshNet
|
||||
/// </summary>
|
||||
private void MessageListener()
|
||||
{
|
||||
#if FEATURE_SOCKET_SELECT
|
||||
var readSockets = new List<Socket> { _socket };
|
||||
#endif // FEATURE_SOCKET_SELECT
|
||||
|
||||
try
|
||||
{
|
||||
var readSockets = new List<Socket> {_socket};
|
||||
|
||||
// remain in message loop until socket is shut down or until we're disconnecting
|
||||
while (_socket.IsConnected())
|
||||
{
|
||||
#if FEATURE_SOCKET_POLL
|
||||
#if FEATURE_SOCKET_SELECT
|
||||
// if the socket is already disposed when Select is invoked, then a SocketException
|
||||
// stating "An operation was attempted on something that is not a socket" is thrown;
|
||||
// we attempt to avoid this exception by having an IsConnected() that can break the
|
||||
@@ -1835,7 +1837,8 @@ namespace Renci.SshNet
|
||||
|
||||
// perform a blocking select to determine whether there's is data available to be
|
||||
// read; we do not use a blocking read to allow us to use Socket.Poll to determine
|
||||
// if the connection is still available (in IsSocketConnected
|
||||
// if the connection is still available (in IsSocketConnected)
|
||||
|
||||
Socket.Select(readSockets, null, null, -1);
|
||||
|
||||
// the Select invocation will be interrupted in one of the following conditions:
|
||||
@@ -1855,7 +1858,19 @@ namespace Renci.SshNet
|
||||
// break out of the message loop
|
||||
break;
|
||||
}
|
||||
#endif // FEATURE_SOCKET_POLL
|
||||
#elif FEATURE_SOCKET_POLL
|
||||
// when Socket.Select(IList, IList, IList, Int32) is not available or is buggy, we use
|
||||
// Socket.Poll(Int, SelectMode) to block until either data is available or the socket
|
||||
// is closed
|
||||
|
||||
var status = _socket.Poll(-1, SelectMode.SelectRead);
|
||||
if (!_socket.IsConnected())
|
||||
{
|
||||
// connection with SSH server was closed;
|
||||
// break out of the message loop
|
||||
break;
|
||||
}
|
||||
#endif // FEATURE_SOCKET_SELECT
|
||||
|
||||
var message = ReceiveMessage();
|
||||
if (message == null)
|
||||
@@ -1869,7 +1884,7 @@ namespace Renci.SshNet
|
||||
message.Process(this);
|
||||
}
|
||||
|
||||
// connection with SSH server was closed
|
||||
// connection with SSH server was closed or socket was disposed
|
||||
RaiseError(CreateConnectionAbortedByServerException());
|
||||
}
|
||||
catch (SocketException ex)
|
||||
@@ -2201,6 +2216,7 @@ namespace Renci.SshNet
|
||||
return;
|
||||
}
|
||||
|
||||
// "save" exception and set exception wait handle to ensure any waits are interrupted
|
||||
_exception = exp;
|
||||
_exceptionWaitHandle.Set();
|
||||
|
||||
|
||||
@@ -2,11 +2,20 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Renci.SshNet.Sftp.Responses;
|
||||
using Renci.SshNet.Channels;
|
||||
|
||||
namespace Renci.SshNet.Sftp
|
||||
{
|
||||
internal interface ISftpSession : ISubsystemSession
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the channel associated with this session.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The channel associated with this session.
|
||||
/// </value>
|
||||
IChannelSession Channel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SFTP protocol version.
|
||||
/// </summary>
|
||||
@@ -95,6 +104,10 @@ namespace Renci.SshNet.Sftp
|
||||
/// <returns>data array; null if EOF</returns>
|
||||
byte[] RequestRead(byte[] handle, ulong offset, uint length);
|
||||
|
||||
SftpReadAsyncResult BeginRead(byte[] handle, ulong offset, uint length, AsyncCallback callback, object state);
|
||||
|
||||
byte[] EndRead(IAsyncResult asyncResult);
|
||||
|
||||
/// <summary>
|
||||
/// Performs SSH_FXP_READDIR request
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
using Renci.SshNet.Abstractions;
|
||||
using Renci.SshNet.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Renci.SshNet.Sftp
|
||||
{
|
||||
internal class SftpFileReader
|
||||
{
|
||||
private const int MaxReadAhead = 10;
|
||||
|
||||
private readonly byte[] _handle;
|
||||
private readonly ISftpSession _sftpSession;
|
||||
private SemaphoreLight _semaphore;
|
||||
private bool _isCompleted;
|
||||
private uint _chunkLength;
|
||||
private int _readAheadChunkIndex;
|
||||
private int _nextChunkIndex;
|
||||
private ulong _readAheadOffset;
|
||||
private ulong _offset;
|
||||
private ulong _fileSize;
|
||||
private readonly IDictionary<int, BufferedRead> _queue;
|
||||
private readonly object _readLock;
|
||||
|
||||
public SftpFileReader(byte[] handle, ISftpSession sftpSession)
|
||||
{
|
||||
_handle = handle;
|
||||
_sftpSession = sftpSession;
|
||||
_chunkLength = 16 * 1024; // TODO !
|
||||
_semaphore = new SemaphoreLight(MaxReadAhead);
|
||||
_queue = new Dictionary<int, BufferedRead>(MaxReadAhead);
|
||||
_readLock = new object();
|
||||
|
||||
_fileSize = (ulong)_sftpSession.RequestFStat(_handle).Size;
|
||||
|
||||
ThreadAbstraction.ExecuteThread(() =>
|
||||
{
|
||||
while (!_isCompleted)
|
||||
{
|
||||
if (_readAheadOffset >= _fileSize)
|
||||
break;
|
||||
|
||||
// TODO implement cancellation!?
|
||||
_semaphore.Wait();
|
||||
|
||||
// start reading next chunk
|
||||
_sftpSession.BeginRead(_handle, _readAheadOffset, _chunkLength, ReadCompleted,
|
||||
new BufferedRead(_readAheadChunkIndex, _readAheadOffset));
|
||||
|
||||
// advance read-ahead offset
|
||||
_readAheadOffset += _chunkLength;
|
||||
|
||||
_readAheadChunkIndex++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public byte[] Read()
|
||||
{
|
||||
lock (_readLock)
|
||||
{
|
||||
BufferedRead nextChunk;
|
||||
|
||||
while (!_queue.TryGetValue(_nextChunkIndex, out nextChunk) && !_isCompleted)
|
||||
Monitor.Wait(_readLock);
|
||||
|
||||
if (_isCompleted)
|
||||
return new byte[0];
|
||||
|
||||
if (nextChunk.Offset == _offset)
|
||||
{
|
||||
var data = nextChunk.Data;
|
||||
_offset += (ulong) data.Length;
|
||||
|
||||
// remove processed chunk
|
||||
_queue.Remove(_nextChunkIndex);
|
||||
// move to next chunk
|
||||
_nextChunkIndex++;
|
||||
// allow read ahead of a new chunk
|
||||
_semaphore.Release();
|
||||
return data;
|
||||
}
|
||||
|
||||
// when the server returned less bytes than requested (for the previous chunk)
|
||||
// we'll synchronously request the remaining data
|
||||
|
||||
var catchUp = new byte[nextChunk.Offset - _offset];
|
||||
var bytesCaughtUp = 0L;
|
||||
|
||||
while (bytesCaughtUp < catchUp.Length)
|
||||
{
|
||||
// TODO: break loop and interrupt blocking wait in case of exception
|
||||
var read = _sftpSession.RequestRead(_handle, _offset, (uint) catchUp.Length);
|
||||
if (read.Length == 0)
|
||||
{
|
||||
// break in loop in "read-ahead" thread (once a blocking wait is interrupted)
|
||||
_isCompleted = true;
|
||||
// interrupt blocking wait in "read-ahead" thread
|
||||
lock (_readLock)
|
||||
Monitor.PulseAll(_readLock);
|
||||
// signal failure
|
||||
throw new SshException("Unexpectedly reached end of file.");
|
||||
}
|
||||
|
||||
bytesCaughtUp += read.Length;
|
||||
_offset += (ulong) bytesCaughtUp;
|
||||
}
|
||||
|
||||
return catchUp;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadCompleted(IAsyncResult result)
|
||||
{
|
||||
var readAsyncResult = result as SftpReadAsyncResult;
|
||||
if (readAsyncResult == null)
|
||||
return;
|
||||
|
||||
var data = readAsyncResult.EndInvoke();
|
||||
if (data.Length == 0)
|
||||
{
|
||||
_isCompleted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var bufferedRead = (BufferedRead)readAsyncResult.AsyncState;
|
||||
bufferedRead.Complete(data);
|
||||
_queue.Add(bufferedRead.ChunkIndex, bufferedRead);
|
||||
}
|
||||
|
||||
// signal that a chunk has been read or EOF has been reached;
|
||||
// in both cases, we want to unblock the "read-ahead" thread
|
||||
lock (_readLock)
|
||||
{
|
||||
Monitor.Pulse(_readLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//private class BufferedReadState
|
||||
//{
|
||||
// private BlockingQueue<BufferedRead> _queue;
|
||||
// private long _offset;
|
||||
|
||||
// public BufferedReadState(long offset, SemaphoreLight semaphore, BlockingQueue<BufferedRead> queue)
|
||||
// {
|
||||
// _queue = queue;
|
||||
// _offset = offset;
|
||||
// _semaphore = semaphore;
|
||||
// }
|
||||
|
||||
// public long Offset
|
||||
// {
|
||||
// get { return _offset; }
|
||||
// }
|
||||
|
||||
// public BlockingQueue<BufferedRead> Queue
|
||||
// {
|
||||
// get { return _queue; }
|
||||
// }
|
||||
|
||||
// public SemaphoreLight Semaphore
|
||||
// {
|
||||
// get { return _semaphore; }
|
||||
// }
|
||||
//}
|
||||
|
||||
//private class BlockingQueue<T>
|
||||
//{
|
||||
// private readonly object _lock = new object();
|
||||
// private Queue<T> _queue;
|
||||
// private bool _isClosed;
|
||||
|
||||
// public BlockingQueue(int capacity)
|
||||
// {
|
||||
// _queue = new Queue<T>(capacity);
|
||||
// }
|
||||
|
||||
// public T Dequeue()
|
||||
// {
|
||||
// lock (_lock)
|
||||
// {
|
||||
// while (!_isClosed && _queue.Count == 0)
|
||||
// Monitor.Wait(_lock);
|
||||
|
||||
// if (_queue.Count == 0)
|
||||
// return default(T);
|
||||
|
||||
// return _queue.Dequeue();
|
||||
// }
|
||||
// }
|
||||
|
||||
// public void Enqueue(T item)
|
||||
// {
|
||||
// lock (_lock)
|
||||
// {
|
||||
// _queue.Enqueue(item);
|
||||
// Monitor.PulseAll(_lock);
|
||||
// }
|
||||
// }
|
||||
|
||||
// public void Close()
|
||||
// {
|
||||
// _isClosed = true;
|
||||
// Monitor.PulseAll(_lock);
|
||||
// }
|
||||
//}
|
||||
|
||||
internal class BufferedRead
|
||||
{
|
||||
public int ChunkIndex { get; private set; }
|
||||
|
||||
public byte[] Data { get; private set; }
|
||||
|
||||
public ulong Offset { get; private set; }
|
||||
|
||||
public BufferedRead(int chunkIndex, ulong offset)
|
||||
{
|
||||
ChunkIndex = chunkIndex;
|
||||
Offset = offset;
|
||||
}
|
||||
|
||||
public void Complete(byte[] data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Renci.SshNet.Common;
|
||||
using System;
|
||||
|
||||
namespace Renci.SshNet.Sftp
|
||||
{
|
||||
internal class SftpReadAsyncResult : AsyncResult<byte[]>
|
||||
{
|
||||
public SftpReadAsyncResult(AsyncCallback asyncCallback, object state) : base(asyncCallback, state)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,6 +169,8 @@ namespace Renci.SshNet.Sftp
|
||||
var offset = 0;
|
||||
var count = data.Length;
|
||||
|
||||
//Console.WriteLine("Data received: " + count);
|
||||
|
||||
// improve performance and reduce GC pressure by not buffering channel data if the received
|
||||
// chunk contains the complete packet data.
|
||||
//
|
||||
@@ -184,6 +186,8 @@ namespace Renci.SshNet.Sftp
|
||||
|
||||
var packetTotalLength = packetDataLength + packetLengthByteCount;
|
||||
|
||||
//Console.WriteLine("Current: " + data.Length + " | Total: " + packetTotalLength);
|
||||
|
||||
// check if complete packet data (or more) is available
|
||||
if (count >= packetTotalLength)
|
||||
{
|
||||
@@ -241,6 +245,8 @@ namespace Renci.SshNet.Sftp
|
||||
|
||||
var packetTotalLength = packetDataLength + packetLengthByteCount;
|
||||
|
||||
//Console.WriteLine("Current: " + data.Length + " | Total: " + packetTotalLength);
|
||||
|
||||
// check if complete packet data is available
|
||||
if (_data.Count < packetTotalLength)
|
||||
{
|
||||
@@ -397,6 +403,46 @@ namespace Renci.SshNet.Sftp
|
||||
}
|
||||
}
|
||||
|
||||
public SftpReadAsyncResult BeginRead(byte[] handle, ulong offset, uint length, AsyncCallback callback, object state)
|
||||
{
|
||||
var asyncResult = new SftpReadAsyncResult(callback, state);
|
||||
|
||||
var request = new SftpReadRequest(ProtocolVersion, NextRequestId, handle, offset, length,
|
||||
response =>
|
||||
{
|
||||
asyncResult.SetAsCompleted(response.Data, false);
|
||||
},
|
||||
response =>
|
||||
{
|
||||
if (response.StatusCode != StatusCodes.Eof)
|
||||
{
|
||||
asyncResult.SetAsCompleted(GetSftpException(response), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
asyncResult.SetAsCompleted(Array<byte>.Empty, false);
|
||||
}
|
||||
});
|
||||
SendRequest(request);
|
||||
|
||||
return asyncResult;
|
||||
}
|
||||
|
||||
public byte[] EndRead(IAsyncResult asyncResult)
|
||||
{
|
||||
if (asyncResult == null)
|
||||
throw new ArgumentNullException("asyncResult");
|
||||
|
||||
var sftpReadAsyncResult = asyncResult as SftpReadAsyncResult;
|
||||
if (sftpReadAsyncResult == null)
|
||||
throw new ArgumentException("IDIOT", "asyncResult");
|
||||
|
||||
if (sftpReadAsyncResult.EndInvokeCalled)
|
||||
throw new InvalidOperationException("EndRead has already been called.");
|
||||
|
||||
return sftpReadAsyncResult.EndInvoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs SSH_FXP_READ request.
|
||||
/// </summary>
|
||||
|
||||
+105
-25
@@ -1996,42 +1996,122 @@ namespace Renci.SshNet
|
||||
var fullPath = _sftpSession.GetCanonicalPath(path);
|
||||
|
||||
var handle = _sftpSession.RequestOpen(fullPath, Flags.Read);
|
||||
|
||||
ulong offset = 0;
|
||||
|
||||
var optimalReadLength = _sftpSession.CalculateOptimalReadLength(_bufferSize);
|
||||
|
||||
var data = _sftpSession.RequestRead(handle, offset, optimalReadLength);
|
||||
|
||||
// Read data while available
|
||||
while (data.Length > 0)
|
||||
var async = true;
|
||||
if (async)
|
||||
{
|
||||
// Cancel download
|
||||
if (asyncResult != null && asyncResult.IsDownloadCanceled)
|
||||
break;
|
||||
var fileReader = new SftpFileReader(handle, _sftpSession);
|
||||
var totalBytesRead = 0UL;
|
||||
|
||||
output.Write(data, 0, data.Length);
|
||||
|
||||
output.Flush();
|
||||
|
||||
offset += (ulong)data.Length;
|
||||
|
||||
// Call callback to report number of bytes read
|
||||
if (downloadCallback != null)
|
||||
while (true)
|
||||
{
|
||||
// copy offset to ensure it's not modified between now and execution of callback
|
||||
var downloadOffset = offset;
|
||||
var data = fileReader.Read();
|
||||
if (data.Length == 0)
|
||||
break;
|
||||
|
||||
// Execute callback on different thread
|
||||
ThreadAbstraction.ExecuteThread(() => { downloadCallback(downloadOffset); });
|
||||
output.Write(data, 0, data.Length);
|
||||
|
||||
totalBytesRead += (ulong) data.Length;
|
||||
|
||||
//Console.WriteLine(totalBytesRead);
|
||||
|
||||
if (downloadCallback != null)
|
||||
downloadCallback(totalBytesRead);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong offset = 0;
|
||||
|
||||
data = _sftpSession.RequestRead(handle, offset, optimalReadLength);
|
||||
var optimalReadLength = _sftpSession.CalculateOptimalReadLength(_bufferSize);
|
||||
|
||||
var data = _sftpSession.RequestRead(handle, offset, optimalReadLength);
|
||||
|
||||
// Read data while available
|
||||
while (data.Length > 0)
|
||||
{
|
||||
// Cancel download
|
||||
if (asyncResult != null && asyncResult.IsDownloadCanceled)
|
||||
break;
|
||||
|
||||
output.Write(data, 0, data.Length);
|
||||
|
||||
output.Flush();
|
||||
|
||||
offset += (ulong)data.Length;
|
||||
|
||||
//Console.WriteLine("" + data.Length + " => " + offset);
|
||||
|
||||
// Call callback to report number of bytes read
|
||||
if (downloadCallback != null)
|
||||
{
|
||||
// copy offset to ensure it's not modified between now and execution of callback
|
||||
var downloadOffset = offset;
|
||||
|
||||
// Execute callback on different thread
|
||||
ThreadAbstraction.ExecuteThread(() => { downloadCallback(downloadOffset); });
|
||||
}
|
||||
|
||||
data = _sftpSession.RequestRead(handle, offset, optimalReadLength);
|
||||
}
|
||||
}
|
||||
|
||||
_sftpSession.RequestClose(handle);
|
||||
}
|
||||
|
||||
private class BlockingQueue<T>
|
||||
{
|
||||
private readonly object _lock = new object();
|
||||
private Queue<T> _queue;
|
||||
private bool _isClosed;
|
||||
|
||||
public BlockingQueue(int capacity)
|
||||
{
|
||||
_queue = new Queue<T>(capacity);
|
||||
}
|
||||
|
||||
public T Dequeue()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
while (!_isClosed && _queue.Count == 0)
|
||||
Monitor.Wait(_lock);
|
||||
|
||||
if (_queue.Count == 0)
|
||||
return default(T);
|
||||
|
||||
return _queue.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_queue.Enqueue(item);
|
||||
Monitor.PulseAll(_lock);
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_isClosed = true;
|
||||
Monitor.PulseAll(_lock);
|
||||
}
|
||||
}
|
||||
|
||||
private class BufferedRead
|
||||
{
|
||||
public byte[] Data { get; private set; }
|
||||
|
||||
public ulong Offset { get; private set; }
|
||||
|
||||
public BufferedRead(ulong offset, byte[] data)
|
||||
{
|
||||
Offset = offset;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internals the upload file.
|
||||
/// </summary>
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Renci.SshNet
|
||||
/// <value>
|
||||
/// The channel associated with this session.
|
||||
/// </value>
|
||||
internal IChannelSession Channel
|
||||
public IChannelSession Channel
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user