mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-10 01:05:42 +00:00
Fix sftp async methods not observing error conditions (#1510)
* Fix sftp async methods not observing error conditions * Update ISftpClient
This commit is contained in:
@@ -56,7 +56,6 @@ namespace Renci.SshNet
|
||||
/// The timeout to wait until an operation completes. The default value is negative
|
||||
/// one (-1) milliseconds, which indicates an infinite timeout period.
|
||||
/// </value>
|
||||
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> represents a value that is less than -1 or greater than <see cref="int.MaxValue"/> milliseconds.</exception>
|
||||
TimeSpan OperationTimeout { get; set; }
|
||||
|
||||
|
||||
@@ -11,12 +11,12 @@ namespace Renci.SshNet
|
||||
internal interface ISubsystemSession : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or set the number of seconds to wait for an operation to complete.
|
||||
/// Gets or sets the number of milliseconds to wait for an operation to complete.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The number of seconds to wait for an operation to complete, or <c>-1</c> to wait indefinitely.
|
||||
/// The number of milliseconds to wait for an operation to complete, or <c>-1</c> to wait indefinitely.
|
||||
/// </value>
|
||||
int OperationTimeout { get; }
|
||||
int OperationTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this session is open.
|
||||
|
||||
@@ -523,28 +523,24 @@ namespace Renci.SshNet.Sftp
|
||||
/// A task that represents the asynchronous <c>SSH_FXP_OPEN</c> request. The value of its
|
||||
/// <see cref="Task{Task}.Result"/> contains the file handle of the specified path.
|
||||
/// </returns>
|
||||
public async Task<byte[]> RequestOpenAsync(string path, Flags flags, CancellationToken cancellationToken)
|
||||
public Task<byte[]> RequestOpenAsync(string path, Flags flags, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<byte[]>(cancellationToken);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
#if NET || NETSTANDARD2_1_OR_GREATER
|
||||
await using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
|
||||
#else
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
|
||||
#endif // NET || NETSTANDARD2_1_OR_GREATER
|
||||
{
|
||||
SendRequest(new SftpOpenRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
flags,
|
||||
response => tcs.TrySetResult(response.Handle),
|
||||
response => tcs.TrySetException(GetSftpException(response))));
|
||||
SendRequest(new SftpOpenRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
flags,
|
||||
response => tcs.TrySetResult(response.Handle),
|
||||
response => tcs.TrySetException(GetSftpException(response))));
|
||||
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -651,8 +647,13 @@ namespace Renci.SshNet.Sftp
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous <c>SSH_FXP_CLOSE</c> request.
|
||||
/// </returns>
|
||||
public async Task RequestCloseAsync(byte[] handle, CancellationToken cancellationToken)
|
||||
public Task RequestCloseAsync(byte[] handle, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
SendRequest(new SftpCloseRequest(ProtocolVersion,
|
||||
@@ -670,17 +671,7 @@ namespace Renci.SshNet.Sftp
|
||||
}
|
||||
}));
|
||||
|
||||
// Only check for cancellation after the SftpCloseRequest was sent
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
#if NET || NETSTANDARD2_1_OR_GREATER
|
||||
await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
|
||||
#else
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
|
||||
#endif // NET || NETSTANDARD2_1_OR_GREATER
|
||||
{
|
||||
_ = await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -875,38 +866,34 @@ namespace Renci.SshNet.Sftp
|
||||
/// its <see cref="Task{Task}.Result"/> contains the data read from the file, or an empty
|
||||
/// array when the end of the file is reached.
|
||||
/// </returns>
|
||||
public async Task<byte[]> RequestReadAsync(byte[] handle, ulong offset, uint length, CancellationToken cancellationToken)
|
||||
public Task<byte[]> RequestReadAsync(byte[] handle, ulong offset, uint length, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<byte[]>(cancellationToken);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
#if NET || NETSTANDARD2_1_OR_GREATER
|
||||
await using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
|
||||
#else
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
|
||||
#endif // NET || NETSTANDARD2_1_OR_GREATER
|
||||
{
|
||||
SendRequest(new SftpReadRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
handle,
|
||||
offset,
|
||||
length,
|
||||
response => tcs.TrySetResult(response.Data),
|
||||
response =>
|
||||
SendRequest(new SftpReadRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
handle,
|
||||
offset,
|
||||
length,
|
||||
response => tcs.TrySetResult(response.Data),
|
||||
response =>
|
||||
{
|
||||
if (response.StatusCode == StatusCodes.Eof)
|
||||
{
|
||||
if (response.StatusCode == StatusCodes.Eof)
|
||||
{
|
||||
_ = tcs.TrySetResult(Array.Empty<byte>());
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = tcs.TrySetException(GetSftpException(response));
|
||||
}
|
||||
}));
|
||||
_ = tcs.TrySetResult(Array.Empty<byte>());
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = tcs.TrySetException(GetSftpException(response));
|
||||
}
|
||||
}));
|
||||
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -972,39 +959,35 @@ namespace Renci.SshNet.Sftp
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous <c>SSH_FXP_WRITE</c> request.
|
||||
/// </returns>
|
||||
public async Task RequestWriteAsync(byte[] handle, ulong serverOffset, byte[] data, int offset, int length, CancellationToken cancellationToken)
|
||||
public Task RequestWriteAsync(byte[] handle, ulong serverOffset, byte[] data, int offset, int length, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
#if NET || NETSTANDARD2_1_OR_GREATER
|
||||
await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
|
||||
#else
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
|
||||
#endif // NET || NETSTANDARD2_1_OR_GREATER
|
||||
{
|
||||
SendRequest(new SftpWriteRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
handle,
|
||||
serverOffset,
|
||||
data,
|
||||
offset,
|
||||
length,
|
||||
response =>
|
||||
{
|
||||
if (response.StatusCode == StatusCodes.Ok)
|
||||
{
|
||||
_ = tcs.TrySetResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = tcs.TrySetException(GetSftpException(response));
|
||||
}
|
||||
}));
|
||||
SendRequest(new SftpWriteRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
handle,
|
||||
serverOffset,
|
||||
data,
|
||||
offset,
|
||||
length,
|
||||
response =>
|
||||
{
|
||||
if (response.StatusCode == StatusCodes.Ok)
|
||||
{
|
||||
_ = tcs.TrySetResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = tcs.TrySetException(GetSftpException(response));
|
||||
}
|
||||
}));
|
||||
|
||||
_ = await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1058,27 +1041,23 @@ namespace Renci.SshNet.Sftp
|
||||
/// A task the represents the asynchronous <c>SSH_FXP_LSTAT</c> request. The value of its
|
||||
/// <see cref="Task{SftpFileAttributes}.Result"/> contains the file attributes of the specified path.
|
||||
/// </returns>
|
||||
public async Task<SftpFileAttributes> RequestLStatAsync(string path, CancellationToken cancellationToken)
|
||||
public Task<SftpFileAttributes> RequestLStatAsync(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<SftpFileAttributes>(cancellationToken);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<SftpFileAttributes>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
#if NET || NETSTANDARD2_1_OR_GREATER
|
||||
await using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileAttributes>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
|
||||
#else
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileAttributes>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
|
||||
#endif // NET || NETSTANDARD2_1_OR_GREATER
|
||||
{
|
||||
SendRequest(new SftpLStatRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
response => tcs.TrySetResult(response.Attributes),
|
||||
response => tcs.TrySetException(GetSftpException(response))));
|
||||
SendRequest(new SftpLStatRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
response => tcs.TrySetResult(response.Attributes),
|
||||
response => tcs.TrySetException(GetSftpException(response))));
|
||||
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1191,26 +1170,22 @@ namespace Renci.SshNet.Sftp
|
||||
/// A task that represents the asynchronous <c>SSH_FXP_FSTAT</c> request. The value of its
|
||||
/// <see cref="Task{Task}.Result"/> contains the file attributes of the specified handle.
|
||||
/// </returns>
|
||||
public async Task<SftpFileAttributes> RequestFStatAsync(byte[] handle, CancellationToken cancellationToken)
|
||||
public Task<SftpFileAttributes> RequestFStatAsync(byte[] handle, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<SftpFileAttributes>(cancellationToken);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<SftpFileAttributes>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
#if NET || NETSTANDARD2_1_OR_GREATER
|
||||
await using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileAttributes>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
|
||||
#else
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileAttributes>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
|
||||
#endif // NET || NETSTANDARD2_1_OR_GREATER
|
||||
{
|
||||
SendRequest(new SftpFStatRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
handle,
|
||||
response => tcs.TrySetResult(response.Attributes),
|
||||
response => tcs.TrySetException(GetSftpException(response))));
|
||||
SendRequest(new SftpFStatRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
handle,
|
||||
response => tcs.TrySetResult(response.Attributes),
|
||||
response => tcs.TrySetException(GetSftpException(response))));
|
||||
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1329,27 +1304,23 @@ namespace Renci.SshNet.Sftp
|
||||
/// A task that represents the asynchronous <c>SSH_FXP_OPENDIR</c> request. The value of its
|
||||
/// <see cref="Task{Task}.Result"/> contains the handle of the specified path.
|
||||
/// </returns>
|
||||
public async Task<byte[]> RequestOpenDirAsync(string path, CancellationToken cancellationToken)
|
||||
public Task<byte[]> RequestOpenDirAsync(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<byte[]>(cancellationToken);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
#if NET || NETSTANDARD2_1_OR_GREATER
|
||||
await using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
|
||||
#else
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
|
||||
#endif // NET || NETSTANDARD2_1_OR_GREATER
|
||||
{
|
||||
SendRequest(new SftpOpenDirRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
response => tcs.TrySetResult(response.Handle),
|
||||
response => tcs.TrySetException(GetSftpException(response))));
|
||||
SendRequest(new SftpOpenDirRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
response => tcs.TrySetResult(response.Handle),
|
||||
response => tcs.TrySetException(GetSftpException(response))));
|
||||
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1410,36 +1381,32 @@ namespace Renci.SshNet.Sftp
|
||||
/// <c>key</c> is the name of a file in the directory and the <c>value</c> is the <see cref="SftpFileAttributes"/>
|
||||
/// of the file.
|
||||
/// </returns>
|
||||
public async Task<KeyValuePair<string, SftpFileAttributes>[]> RequestReadDirAsync(byte[] handle, CancellationToken cancellationToken)
|
||||
public Task<KeyValuePair<string, SftpFileAttributes>[]> RequestReadDirAsync(byte[] handle, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<KeyValuePair<string, SftpFileAttributes>[]>(cancellationToken);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
#if NET || NETSTANDARD2_1_OR_GREATER
|
||||
await using (cancellationToken.Register(s => ((TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
|
||||
#else
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
|
||||
#endif // NET || NETSTANDARD2_1_OR_GREATER
|
||||
{
|
||||
SendRequest(new SftpReadDirRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
handle,
|
||||
response => tcs.TrySetResult(response.Files),
|
||||
response =>
|
||||
SendRequest(new SftpReadDirRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
handle,
|
||||
response => tcs.TrySetResult(response.Files),
|
||||
response =>
|
||||
{
|
||||
if (response.StatusCode == StatusCodes.Eof)
|
||||
{
|
||||
if (response.StatusCode == StatusCodes.Eof)
|
||||
{
|
||||
_ = tcs.TrySetResult(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = tcs.TrySetException(GetSftpException(response));
|
||||
}
|
||||
}));
|
||||
_ = tcs.TrySetResult(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = tcs.TrySetException(GetSftpException(response));
|
||||
}
|
||||
}));
|
||||
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1481,36 +1448,32 @@ namespace Renci.SshNet.Sftp
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous <c>SSH_FXP_REMOVE</c> request.
|
||||
/// </returns>
|
||||
public async Task RequestRemoveAsync(string path, CancellationToken cancellationToken)
|
||||
public Task RequestRemoveAsync(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
#if NET || NETSTANDARD2_1_OR_GREATER
|
||||
await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
|
||||
#else
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
|
||||
#endif // NET || NETSTANDARD2_1_OR_GREATER
|
||||
{
|
||||
SendRequest(new SftpRemoveRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
response =>
|
||||
{
|
||||
if (response.StatusCode == StatusCodes.Ok)
|
||||
{
|
||||
_ = tcs.TrySetResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = tcs.TrySetException(GetSftpException(response));
|
||||
}
|
||||
}));
|
||||
SendRequest(new SftpRemoveRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
response =>
|
||||
{
|
||||
if (response.StatusCode == StatusCodes.Ok)
|
||||
{
|
||||
_ = tcs.TrySetResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = tcs.TrySetException(GetSftpException(response));
|
||||
}
|
||||
}));
|
||||
|
||||
_ = await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1550,36 +1513,32 @@ namespace Renci.SshNet.Sftp
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
|
||||
/// <returns>A <see cref="Task"/> that represents the asynchronous <c>SSH_FXP_MKDIR</c> operation.</returns>
|
||||
public async Task RequestMkDirAsync(string path, CancellationToken cancellationToken = default)
|
||||
public Task RequestMkDirAsync(string path, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
#if NET || NETSTANDARD2_1_OR_GREATER
|
||||
await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
|
||||
#else
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
|
||||
#endif // NET || NETSTANDARD2_1_OR_GREATER
|
||||
{
|
||||
SendRequest(new SftpMkDirRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
response =>
|
||||
SendRequest(new SftpMkDirRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
response =>
|
||||
{
|
||||
if (response.StatusCode == StatusCodes.Ok)
|
||||
{
|
||||
if (response.StatusCode == StatusCodes.Ok)
|
||||
{
|
||||
_ = tcs.TrySetResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
tcs.TrySetException(GetSftpException(response));
|
||||
}
|
||||
}));
|
||||
_ = tcs.TrySetResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = tcs.TrySetException(GetSftpException(response));
|
||||
}
|
||||
}));
|
||||
|
||||
_ = await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1614,37 +1573,33 @@ namespace Renci.SshNet.Sftp
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task RequestRmDirAsync(string path, CancellationToken cancellationToken = default)
|
||||
public Task RequestRmDirAsync(string path, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
#if NET || NETSTANDARD2_1_OR_GREATER
|
||||
await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
|
||||
#else
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
|
||||
#endif // NET || NETSTANDARD2_1_OR_GREATER
|
||||
{
|
||||
SendRequest(new SftpRmDirRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
response =>
|
||||
SendRequest(new SftpRmDirRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
response =>
|
||||
{
|
||||
var exception = GetSftpException(response);
|
||||
if (exception is not null)
|
||||
{
|
||||
var exception = GetSftpException(response);
|
||||
if (exception is not null)
|
||||
{
|
||||
tcs.TrySetException(exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
tcs.TrySetResult(true);
|
||||
}
|
||||
}));
|
||||
_ = tcs.TrySetException(exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = tcs.TrySetResult(true);
|
||||
}
|
||||
}));
|
||||
|
||||
_ = await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1691,37 +1646,33 @@ namespace Renci.SshNet.Sftp
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task<KeyValuePair<string, SftpFileAttributes>[]> RequestRealPathAsync(string path, bool nullOnError, CancellationToken cancellationToken)
|
||||
internal Task<KeyValuePair<string, SftpFileAttributes>[]> RequestRealPathAsync(string path, bool nullOnError, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<KeyValuePair<string, SftpFileAttributes>[]>(cancellationToken);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
#if NET || NETSTANDARD2_1_OR_GREATER
|
||||
await using (cancellationToken.Register(s => ((TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
|
||||
#else
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
|
||||
#endif // NET || NETSTANDARD2_1_OR_GREATER
|
||||
{
|
||||
SendRequest(new SftpRealPathRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
response => tcs.TrySetResult(response.Files),
|
||||
response =>
|
||||
SendRequest(new SftpRealPathRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
response => tcs.TrySetResult(response.Files),
|
||||
response =>
|
||||
{
|
||||
if (nullOnError)
|
||||
{
|
||||
if (nullOnError)
|
||||
{
|
||||
_ = tcs.TrySetResult(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = tcs.TrySetException(GetSftpException(response));
|
||||
}
|
||||
}));
|
||||
_ = tcs.TrySetResult(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = tcs.TrySetException(GetSftpException(response));
|
||||
}
|
||||
}));
|
||||
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1921,37 +1872,33 @@ namespace Renci.SshNet.Sftp
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous <c>SSH_FXP_RENAME</c> request.
|
||||
/// </returns>
|
||||
public async Task RequestRenameAsync(string oldPath, string newPath, CancellationToken cancellationToken)
|
||||
public Task RequestRenameAsync(string oldPath, string newPath, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
#if NET || NETSTANDARD2_1_OR_GREATER
|
||||
await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
|
||||
#else
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
|
||||
#endif // NET || NETSTANDARD2_1_OR_GREATER
|
||||
{
|
||||
SendRequest(new SftpRenameRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
oldPath,
|
||||
newPath,
|
||||
_encoding,
|
||||
response =>
|
||||
{
|
||||
if (response.StatusCode == StatusCodes.Ok)
|
||||
{
|
||||
_ = tcs.TrySetResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = tcs.TrySetException(GetSftpException(response));
|
||||
}
|
||||
}));
|
||||
SendRequest(new SftpRenameRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
oldPath,
|
||||
newPath,
|
||||
_encoding,
|
||||
response =>
|
||||
{
|
||||
if (response.StatusCode == StatusCodes.Ok)
|
||||
{
|
||||
_ = tcs.TrySetResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = tcs.TrySetException(GetSftpException(response));
|
||||
}
|
||||
}));
|
||||
|
||||
_ = await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -2149,32 +2096,28 @@ namespace Renci.SshNet.Sftp
|
||||
/// <see cref="Task{Task}.Result"/> contains the file system information for the specified
|
||||
/// path.
|
||||
/// </returns>
|
||||
public async Task<SftpFileSystemInformation> RequestStatVfsAsync(string path, CancellationToken cancellationToken)
|
||||
public Task<SftpFileSystemInformation> RequestStatVfsAsync(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
if (ProtocolVersion < 3)
|
||||
{
|
||||
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<SftpFileSystemInformation>(cancellationToken);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<SftpFileSystemInformation>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
#if NET || NETSTANDARD2_1_OR_GREATER
|
||||
await using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileSystemInformation>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
|
||||
#else
|
||||
using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileSystemInformation>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
|
||||
#endif // NET || NETSTANDARD2_1_OR_GREATER
|
||||
{
|
||||
SendRequest(new StatVfsRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
response => tcs.TrySetResult(response.GetReply<StatVfsReplyInfo>().Information),
|
||||
response => tcs.TrySetException(GetSftpException(response))));
|
||||
SendRequest(new StatVfsRequest(ProtocolVersion,
|
||||
NextRequestId,
|
||||
path,
|
||||
_encoding,
|
||||
response => tcs.TrySetResult(response.GetReply<StatVfsReplyInfo>().Information),
|
||||
response => tcs.TrySetException(GetSftpException(response))));
|
||||
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
return WaitOnHandleAsync(tcs, OperationTimeout, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -46,21 +46,21 @@ namespace Renci.SshNet
|
||||
/// The timeout to wait until an operation completes. The default value is negative
|
||||
/// one (-1) milliseconds, which indicates an infinite timeout period.
|
||||
/// </value>
|
||||
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> represents a value that is less than -1 or greater than <see cref="int.MaxValue"/> milliseconds.</exception>
|
||||
public TimeSpan OperationTimeout
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
return TimeSpan.FromMilliseconds(_operationTimeout);
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
_operationTimeout = value.AsTimeout(nameof(OperationTimeout));
|
||||
|
||||
if (_sftpSession is { } sftpSession)
|
||||
{
|
||||
sftpSession.OperationTimeout = _operationTimeout;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Globalization;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
using Renci.SshNet.Channels;
|
||||
@@ -29,13 +30,8 @@ namespace Renci.SshNet
|
||||
private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(initialState: false);
|
||||
private bool _isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or set the number of seconds to wait for an operation to complete.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The number of seconds to wait for an operation to complete, or -1 to wait indefinitely.
|
||||
/// </value>
|
||||
public int OperationTimeout { get; private set; }
|
||||
/// <inheritdoc/>
|
||||
public int OperationTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when an error occurred.
|
||||
@@ -250,6 +246,59 @@ namespace Renci.SshNet
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task<T> WaitOnHandleAsync<T>(TaskCompletionSource<T> tcs, int millisecondsTimeout, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var errorOccuredReg = ThreadPool.RegisterWaitForSingleObject(
|
||||
_errorOccuredWaitHandle,
|
||||
(tcs, _) => ((TaskCompletionSource<T>)tcs).TrySetException(_exception),
|
||||
state: tcs,
|
||||
millisecondsTimeOutInterval: -1,
|
||||
executeOnlyOnce: true);
|
||||
|
||||
var sessionDisconnectedReg = ThreadPool.RegisterWaitForSingleObject(
|
||||
_sessionDisconnectedWaitHandle,
|
||||
static (tcs, _) => ((TaskCompletionSource<T>)tcs).TrySetException(new SshException("Connection was closed by the server.")),
|
||||
state: tcs,
|
||||
millisecondsTimeOutInterval: -1,
|
||||
executeOnlyOnce: true);
|
||||
|
||||
var channelClosedReg = ThreadPool.RegisterWaitForSingleObject(
|
||||
_channelClosedWaitHandle,
|
||||
static (tcs, _) => ((TaskCompletionSource<T>)tcs).TrySetException(new SshException("Channel was closed.")),
|
||||
state: tcs,
|
||||
millisecondsTimeOutInterval: -1,
|
||||
executeOnlyOnce: true);
|
||||
|
||||
using var timeoutCts = new CancellationTokenSource(millisecondsTimeout);
|
||||
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
|
||||
|
||||
using var tokenReg = linkedCts.Token.Register(
|
||||
static s =>
|
||||
{
|
||||
(var tcs, var cancellationToken) = ((TaskCompletionSource<T>, CancellationToken))s;
|
||||
_ = tcs.TrySetCanceled(cancellationToken);
|
||||
},
|
||||
state: (tcs, cancellationToken),
|
||||
useSynchronizationContext: false);
|
||||
|
||||
try
|
||||
{
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException oce) when (timeoutCts.IsCancellationRequested)
|
||||
{
|
||||
throw new SshOperationTimeoutException("Operation has timed out.", oce);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ = errorOccuredReg.Unregister(waitObject: null);
|
||||
_ = sessionDisconnectedReg.Unregister(waitObject: null);
|
||||
_ = channelClosedReg.Unregister(waitObject: null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blocks the current thread until the specified <see cref="WaitHandle"/> gets signaled, using a
|
||||
/// 32-bit signed integer to specify the time interval in milliseconds.
|
||||
|
||||
@@ -115,37 +115,5 @@ namespace Renci.SshNet.Tests.Classes
|
||||
Assert.AreEqual("OperationTimeout", ex.ParamName);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OperationTimeout_Disposed()
|
||||
{
|
||||
var connectionInfo = new PasswordConnectionInfo("host", 22, "admin", "pwd");
|
||||
var target = new SftpClient(connectionInfo);
|
||||
target.Dispose();
|
||||
|
||||
// getter
|
||||
try
|
||||
{
|
||||
var actual = target.OperationTimeout;
|
||||
Assert.Fail("Should have failed, but returned: " + actual);
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual(typeof(SftpClient).FullName, ex.ObjectName);
|
||||
}
|
||||
|
||||
// setter
|
||||
try
|
||||
{
|
||||
target.OperationTimeout = TimeSpan.FromMilliseconds(5);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual(typeof(SftpClient).FullName, ex.ObjectName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
#if !NET8_0_OR_GREATER
|
||||
using Renci.SshNet.Abstractions;
|
||||
#endif
|
||||
using Renci.SshNet.Channels;
|
||||
using Renci.SshNet.Common;
|
||||
using Renci.SshNet.Connection;
|
||||
using Renci.SshNet.Messages;
|
||||
using Renci.SshNet.Messages.Authentication;
|
||||
using Renci.SshNet.Messages.Connection;
|
||||
using Renci.SshNet.Sftp;
|
||||
using Renci.SshNet.Sftp.Responses;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
[TestClass]
|
||||
public class SftpClientTest_AsyncExceptions
|
||||
{
|
||||
private MySession _session;
|
||||
private SftpClient _client;
|
||||
|
||||
[TestInitialize]
|
||||
public void Init()
|
||||
{
|
||||
var socketFactoryMock = new Mock<ISocketFactory>(MockBehavior.Strict);
|
||||
var serviceFactoryMock = new Mock<IServiceFactory>(MockBehavior.Strict);
|
||||
|
||||
var connInfo = new PasswordConnectionInfo("host", "user", "pwd");
|
||||
|
||||
_session = new MySession(connInfo);
|
||||
|
||||
var concreteServiceFactory = new ServiceFactory();
|
||||
|
||||
serviceFactoryMock
|
||||
.Setup(p => p.CreateSocketFactory())
|
||||
.Returns(socketFactoryMock.Object);
|
||||
|
||||
serviceFactoryMock
|
||||
.Setup(p => p.CreateSession(It.IsAny<ConnectionInfo>(), socketFactoryMock.Object))
|
||||
.Returns(_session);
|
||||
|
||||
serviceFactoryMock
|
||||
.Setup(p => p.CreateSftpResponseFactory())
|
||||
.Returns(concreteServiceFactory.CreateSftpResponseFactory);
|
||||
|
||||
serviceFactoryMock
|
||||
.Setup(p => p.CreateSftpSession(_session, It.IsAny<int>(), It.IsAny<Encoding>(), It.IsAny<ISftpResponseFactory>()))
|
||||
.Returns(concreteServiceFactory.CreateSftpSession);
|
||||
|
||||
_client = new SftpClient(connInfo, false, serviceFactoryMock.Object);
|
||||
_client.Connect();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Async_ObservesSessionDisconnected()
|
||||
{
|
||||
Task<SftpFileStream> openTask = _client.OpenAsync("path", FileMode.Create, FileAccess.Write, CancellationToken.None);
|
||||
|
||||
Assert.IsFalse(openTask.IsCompleted);
|
||||
|
||||
_session.InvokeDisconnected();
|
||||
|
||||
var ex = await Assert.ThrowsExceptionAsync<SshException>(() => openTask);
|
||||
Assert.AreEqual("Connection was closed by the server.", ex.Message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Async_ObservesChannelClosed()
|
||||
{
|
||||
Task<SftpFileStream> openTask = _client.OpenAsync("path", FileMode.Create, FileAccess.Write, CancellationToken.None);
|
||||
|
||||
Assert.IsFalse(openTask.IsCompleted);
|
||||
|
||||
_session.InvokeChannelCloseReceived();
|
||||
|
||||
var ex = await Assert.ThrowsExceptionAsync<SshException>(() => openTask);
|
||||
Assert.AreEqual("Channel was closed.", ex.Message);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Async_ObservesCancellationToken()
|
||||
{
|
||||
using CancellationTokenSource cts = new();
|
||||
|
||||
Task<SftpFileStream> openTask = _client.OpenAsync("path", FileMode.Create, FileAccess.Write, cts.Token);
|
||||
|
||||
Assert.IsFalse(openTask.IsCompleted);
|
||||
|
||||
await cts.CancelAsync();
|
||||
|
||||
var ex = await Assert.ThrowsExceptionAsync<TaskCanceledException>(() => openTask);
|
||||
Assert.AreEqual(cts.Token, ex.CancellationToken);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Async_ObservesOperationTimeout()
|
||||
{
|
||||
_client.OperationTimeout = TimeSpan.FromMilliseconds(250);
|
||||
|
||||
Task<SftpFileStream> openTask = _client.OpenAsync("path", FileMode.Create, FileAccess.Write, CancellationToken.None);
|
||||
|
||||
var ex = await Assert.ThrowsExceptionAsync<SshOperationTimeoutException>(() => openTask);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Async_ObservesErrorOccurred()
|
||||
{
|
||||
Task<SftpFileStream> openTask = _client.OpenAsync("path", FileMode.Create, FileAccess.Write, CancellationToken.None);
|
||||
|
||||
Assert.IsFalse(openTask.IsCompleted);
|
||||
|
||||
MyException ex = new("my exception");
|
||||
|
||||
_session.InvokeErrorOccurred(ex);
|
||||
|
||||
var ex2 = await Assert.ThrowsExceptionAsync<MyException>(() => openTask);
|
||||
Assert.AreEqual(ex.Message, ex2.Message);
|
||||
}
|
||||
|
||||
#pragma warning disable IDE0022 // Use block body for method
|
||||
#pragma warning disable IDE0025 // Use block body for property
|
||||
#pragma warning disable CS0067 // event is unused
|
||||
private class MySession(ConnectionInfo connectionInfo) : ISession
|
||||
{
|
||||
public IConnectionInfo ConnectionInfo => connectionInfo;
|
||||
|
||||
public event EventHandler<MessageEventArgs<ChannelCloseMessage>> ChannelCloseReceived;
|
||||
public event EventHandler<MessageEventArgs<ChannelDataMessage>> ChannelDataReceived;
|
||||
public event EventHandler<MessageEventArgs<ChannelEofMessage>> ChannelEofReceived;
|
||||
public event EventHandler<MessageEventArgs<ChannelExtendedDataMessage>> ChannelExtendedDataReceived;
|
||||
public event EventHandler<MessageEventArgs<ChannelFailureMessage>> ChannelFailureReceived;
|
||||
public event EventHandler<MessageEventArgs<ChannelOpenConfirmationMessage>> ChannelOpenConfirmationReceived;
|
||||
public event EventHandler<MessageEventArgs<ChannelOpenFailureMessage>> ChannelOpenFailureReceived;
|
||||
public event EventHandler<MessageEventArgs<ChannelOpenMessage>> ChannelOpenReceived;
|
||||
public event EventHandler<MessageEventArgs<ChannelRequestMessage>> ChannelRequestReceived;
|
||||
public event EventHandler<MessageEventArgs<ChannelSuccessMessage>> ChannelSuccessReceived;
|
||||
public event EventHandler<MessageEventArgs<ChannelWindowAdjustMessage>> ChannelWindowAdjustReceived;
|
||||
public event EventHandler<EventArgs> Disconnected;
|
||||
public event EventHandler<ExceptionEventArgs> ErrorOccured;
|
||||
public event EventHandler<SshIdentificationEventArgs> ServerIdentificationReceived;
|
||||
public event EventHandler<HostKeyEventArgs> HostKeyReceived;
|
||||
public event EventHandler<MessageEventArgs<RequestSuccessMessage>> RequestSuccessReceived;
|
||||
public event EventHandler<MessageEventArgs<RequestFailureMessage>> RequestFailureReceived;
|
||||
public event EventHandler<MessageEventArgs<BannerMessage>> UserAuthenticationBannerReceived;
|
||||
|
||||
public void InvokeDisconnected()
|
||||
{
|
||||
Disconnected?.Invoke(this, new EventArgs());
|
||||
}
|
||||
|
||||
public void InvokeChannelCloseReceived()
|
||||
{
|
||||
ChannelCloseReceived?.Invoke(
|
||||
this,
|
||||
new MessageEventArgs<ChannelCloseMessage>(new ChannelCloseMessage(0)));
|
||||
}
|
||||
|
||||
public void InvokeErrorOccurred(Exception ex)
|
||||
{
|
||||
ErrorOccured?.Invoke(this, new ExceptionEventArgs(ex));
|
||||
}
|
||||
|
||||
public void SendMessage(Message message)
|
||||
{
|
||||
if (message is ChannelOpenMessage)
|
||||
{
|
||||
ChannelOpenConfirmationReceived?.Invoke(
|
||||
this,
|
||||
new MessageEventArgs<ChannelOpenConfirmationMessage>(
|
||||
new ChannelOpenConfirmationMessage(0, int.MaxValue, int.MaxValue, 0)));
|
||||
}
|
||||
else if (message is ChannelRequestMessage)
|
||||
{
|
||||
ChannelSuccessReceived?.Invoke(
|
||||
this,
|
||||
new MessageEventArgs<ChannelSuccessMessage>(new ChannelSuccessMessage(0)));
|
||||
}
|
||||
else if (message is ChannelDataMessage dataMsg)
|
||||
{
|
||||
if (dataMsg.Data[sizeof(uint)] == (byte)SftpMessageTypes.Init)
|
||||
{
|
||||
ChannelDataReceived?.Invoke(
|
||||
this,
|
||||
new MessageEventArgs<ChannelDataMessage>(
|
||||
new ChannelDataMessage(0, new SftpVersionResponse() { Version = 3 }.GetBytes())));
|
||||
}
|
||||
else if (dataMsg.Data[sizeof(uint)] == (byte)SftpMessageTypes.RealPath)
|
||||
{
|
||||
ChannelDataReceived?.Invoke(
|
||||
this,
|
||||
new MessageEventArgs<ChannelDataMessage>(
|
||||
new ChannelDataMessage(0,
|
||||
new SftpNameResponse(3, Encoding.UTF8)
|
||||
{
|
||||
ResponseId = 1,
|
||||
Files = [new("thepath", new SftpFileAttributes(default, default, default, default, default, default, default))]
|
||||
}.GetBytes())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsConnected => false;
|
||||
|
||||
public SemaphoreSlim SessionSemaphore { get; } = new(1);
|
||||
|
||||
public IChannelSession CreateChannelSession() => new ChannelSession(this, 0, int.MaxValue, int.MaxValue);
|
||||
|
||||
public WaitHandle MessageListenerCompleted => throw new NotImplementedException();
|
||||
|
||||
public void Connect()
|
||||
{
|
||||
}
|
||||
|
||||
public Task ConnectAsync(CancellationToken cancellationToken) => throw new NotImplementedException();
|
||||
|
||||
public IChannelDirectTcpip CreateChannelDirectTcpip() => throw new NotImplementedException();
|
||||
|
||||
public IChannelForwardedTcpip CreateChannelForwardedTcpip(uint remoteChannelNumber, uint remoteWindowSize, uint remoteChannelDataPacketSize)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnDisconnecting()
|
||||
{
|
||||
}
|
||||
|
||||
public void Disconnect() => throw new NotImplementedException();
|
||||
|
||||
public void RegisterMessage(string messageName) => throw new NotImplementedException();
|
||||
|
||||
public bool TrySendMessage(Message message) => throw new NotImplementedException();
|
||||
|
||||
public WaitResult TryWait(WaitHandle waitHandle, TimeSpan timeout, out Exception exception) => throw new NotImplementedException();
|
||||
|
||||
public WaitResult TryWait(WaitHandle waitHandle, TimeSpan timeout) => throw new NotImplementedException();
|
||||
|
||||
public void UnRegisterMessage(string messageName) => throw new NotImplementedException();
|
||||
|
||||
public void WaitOnHandle(WaitHandle waitHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public void WaitOnHandle(WaitHandle waitHandle, TimeSpan timeout) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup() => _client?.Dispose();
|
||||
|
||||
#pragma warning disable
|
||||
private class MyException : Exception
|
||||
{
|
||||
public MyException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user