|
|
@@ -1,13 +1,34 @@
|
|
|
using System;
|
|
|
using System.Threading;
|
|
|
+using System.Threading.Tasks;
|
|
|
|
|
|
namespace Renci.SshNet.Sftp
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// Represents the status of an asynchronous SFTP operation.
|
|
|
/// </summary>
|
|
|
- public class SftpAsyncResult : IAsyncResult, IDisposable
|
|
|
+ public class SftpAsyncResult : IAsyncResult
|
|
|
{
|
|
|
+ private const int _statePending = 0;
|
|
|
+
|
|
|
+ private const int _stateCompletedSynchronously = 1;
|
|
|
+
|
|
|
+ private const int _stateCompletedAsynchronously = 2;
|
|
|
+
|
|
|
+ private readonly AsyncCallback _asyncCallback;
|
|
|
+
|
|
|
+ private readonly Object _asyncState;
|
|
|
+
|
|
|
+ private Exception _exception;
|
|
|
+
|
|
|
+ private ManualResetEvent _asyncWaitHandle;
|
|
|
+
|
|
|
+ private int _completedState = _statePending;
|
|
|
+
|
|
|
+ private SftpSession _sftpSession;
|
|
|
+
|
|
|
+ private TimeSpan _commandTimeout;
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Gets or sets the uploaded bytes.
|
|
|
/// </summary>
|
|
|
@@ -20,35 +41,61 @@ namespace Renci.SshNet.Sftp
|
|
|
/// <value>The downloaded bytes.</value>
|
|
|
public ulong DownloadedBytes { get; internal set; }
|
|
|
|
|
|
- private SftpCommand _command;
|
|
|
-
|
|
|
/// <summary>
|
|
|
/// Initializes a new instance of the <see cref="SftpAsyncResult"/> class.
|
|
|
/// </summary>
|
|
|
- /// <param name="command">The command.</param>
|
|
|
+ /// <param name="sftpSession">The SFTP session.</param>
|
|
|
+ /// <param name="commandTimeout">The command timeout.</param>
|
|
|
+ /// <param name="asyncCallback">The async callback.</param>
|
|
|
/// <param name="state">The state.</param>
|
|
|
- internal SftpAsyncResult(SftpCommand command, object state)
|
|
|
+ internal SftpAsyncResult(SftpSession sftpSession, TimeSpan commandTimeout, AsyncCallback asyncCallback, object state)
|
|
|
{
|
|
|
- this._command = command;
|
|
|
- this.AsyncState = state;
|
|
|
- this.AsyncWaitHandle = new ManualResetEvent(false);
|
|
|
+ this._sftpSession = sftpSession;
|
|
|
+ this._commandTimeout = commandTimeout;
|
|
|
+ this._asyncCallback = asyncCallback;
|
|
|
+ this._asyncState = state;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Gets the command.
|
|
|
+ /// Marks result as completed.
|
|
|
/// </summary>
|
|
|
- /// <typeparam name="T">Type of the command</typeparam>
|
|
|
- /// <returns></returns>
|
|
|
- internal T GetCommand<T>() where T : SftpCommand
|
|
|
+ /// <param name="exception">The exception.</param>
|
|
|
+ /// <param name="completedSynchronously">if set to <c>true</c> [completed synchronously].</param>
|
|
|
+ public void SetAsCompleted(Exception exception, bool completedSynchronously)
|
|
|
{
|
|
|
- T cmd = this._command as T;
|
|
|
+ // Passing null for exception means no error occurred.
|
|
|
+ // This is the common case
|
|
|
+ this._exception = exception;
|
|
|
+
|
|
|
+ // The _completedState field MUST be set prior calling the callback
|
|
|
+ Int32 prevState = Interlocked.Exchange(ref this._completedState, completedSynchronously ? _stateCompletedSynchronously : _stateCompletedAsynchronously);
|
|
|
+ if (prevState != _statePending)
|
|
|
+ throw new InvalidOperationException("You can set a result only once");
|
|
|
+
|
|
|
+ // If the event exists, set it
|
|
|
+ if (this._asyncWaitHandle != null)
|
|
|
+ this._asyncWaitHandle.Set();
|
|
|
+
|
|
|
+ // If a callback method was set, call it on different thread
|
|
|
+ if (this._asyncCallback != null)
|
|
|
+ Task.Factory.StartNew(() => { this._asyncCallback(this); });
|
|
|
+ }
|
|
|
|
|
|
- if (cmd == null)
|
|
|
+ public void EndInvoke()
|
|
|
+ {
|
|
|
+ // This method assumes that only 1 thread calls EndInvoke
|
|
|
+ // for this object
|
|
|
+ if (!this.IsCompleted)
|
|
|
{
|
|
|
- throw new InvalidOperationException("Not valid IAsyncResult object.");
|
|
|
+ // If the operation isn't done, wait for it
|
|
|
+ this._sftpSession.WaitHandle(this.AsyncWaitHandle, this._commandTimeout);
|
|
|
+ this.AsyncWaitHandle.Close();
|
|
|
+ this._asyncWaitHandle = null; // Allow early GC
|
|
|
}
|
|
|
|
|
|
- return cmd;
|
|
|
+ // Operation is done: if an exception occurred, throw it
|
|
|
+ if (this._exception != null)
|
|
|
+ throw _exception;
|
|
|
}
|
|
|
|
|
|
#region IAsyncResult Members
|
|
|
@@ -57,89 +104,64 @@ namespace Renci.SshNet.Sftp
|
|
|
/// Gets a user-defined object that qualifies or contains information about an asynchronous operation.
|
|
|
/// </summary>
|
|
|
/// <returns>A user-defined object that qualifies or contains information about an asynchronous operation.</returns>
|
|
|
- public object AsyncState { get; private set; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets a <see cref="T:System.Threading.WaitHandle"/> that is used to wait for an asynchronous operation to complete.
|
|
|
- /// </summary>
|
|
|
- /// <returns>A <see cref="T:System.Threading.WaitHandle"/> that is used to wait for an asynchronous operation to complete.</returns>
|
|
|
- public WaitHandle AsyncWaitHandle { get; private set; }
|
|
|
+ public Object AsyncState { get { return this._asyncState; } }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Gets a value that indicates whether the asynchronous operation completed synchronously.
|
|
|
/// </summary>
|
|
|
/// <returns>true if the asynchronous operation completed synchronously; otherwise, false.</returns>
|
|
|
- public bool CompletedSynchronously { get; private set; }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets a value that indicates whether the asynchronous operation has completed.
|
|
|
- /// </summary>
|
|
|
- /// <returns>true if the operation is complete; otherwise, false.</returns>
|
|
|
- public bool IsCompleted { get; private set; }
|
|
|
-
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region IDisposable Members
|
|
|
-
|
|
|
- private bool _disposed = false;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
|
|
- /// </summary>
|
|
|
- public void Dispose()
|
|
|
+ public Boolean CompletedSynchronously
|
|
|
{
|
|
|
- Dispose(true);
|
|
|
-
|
|
|
- GC.SuppressFinalize(this);
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return Thread.VolatileRead(ref this._completedState) == _stateCompletedSynchronously;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Releases unmanaged and - optionally - managed resources
|
|
|
+ /// Gets a <see cref="T:System.Threading.WaitHandle"/> that is used to wait for an asynchronous operation to complete.
|
|
|
/// </summary>
|
|
|
- /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
|
|
- protected virtual void Dispose(bool disposing)
|
|
|
+ /// <returns>A <see cref="T:System.Threading.WaitHandle"/> that is used to wait for an asynchronous operation to complete.</returns>
|
|
|
+ public WaitHandle AsyncWaitHandle
|
|
|
{
|
|
|
- // Check to see if Dispose has already been called.
|
|
|
- if (!this._disposed)
|
|
|
+ get
|
|
|
{
|
|
|
- // If disposing equals true, dispose all managed
|
|
|
- // and unmanaged resources.
|
|
|
- if (disposing)
|
|
|
+ if (this._asyncWaitHandle == null)
|
|
|
{
|
|
|
- // Dispose managed resources.
|
|
|
- if (this.AsyncWaitHandle != null)
|
|
|
+ Boolean done = this.IsCompleted;
|
|
|
+ ManualResetEvent mre = new ManualResetEvent(done);
|
|
|
+ if (Interlocked.CompareExchange(ref this._asyncWaitHandle, mre, null) != null)
|
|
|
{
|
|
|
- this.AsyncWaitHandle.Dispose();
|
|
|
- this.AsyncWaitHandle = null;
|
|
|
+ // Another thread created this object's event; dispose
|
|
|
+ // the event we just created
|
|
|
+ mre.Close();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (!done && this.IsCompleted)
|
|
|
+ {
|
|
|
+ // If the operation wasn't done when we created
|
|
|
+ // the event but now it is done, set the event
|
|
|
+ this._asyncWaitHandle.Set();
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- // Note disposing has been done.
|
|
|
- this._disposed = true;
|
|
|
+ return this._asyncWaitHandle;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Releases unmanaged resources and performs other cleanup operations before the
|
|
|
- /// <see cref="SftpAsyncResult"/> is reclaimed by garbage collection.
|
|
|
+ /// Gets a value that indicates whether the asynchronous operation has completed.
|
|
|
/// </summary>
|
|
|
- ~SftpAsyncResult()
|
|
|
+ /// <returns>true if the operation is complete; otherwise, false.</returns>
|
|
|
+ public Boolean IsCompleted
|
|
|
{
|
|
|
- // Do not re-create Dispose clean-up code here.
|
|
|
- // Calling Dispose(false) is optimal in terms of
|
|
|
- // readability and maintainability.
|
|
|
- Dispose(false);
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return Thread.VolatileRead(ref this._completedState) != _statePending;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Completes asynchronous operation.
|
|
|
- /// </summary>
|
|
|
- internal void Complete()
|
|
|
- {
|
|
|
- this.IsCompleted = true;
|
|
|
- ((EventWaitHandle)this.AsyncWaitHandle).Set();
|
|
|
- }
|
|
|
}
|
|
|
}
|