public void UploadFile(string localPath, string remotePath, Cancellation cancellation, SFTPFileTransferProgressDelegate progressDelegate)
{
CheckStatus();
uint requestId = ++_requestId;
ulong transmitted = 0;
Exception pendingException = null;
bool hasError = false;
bool dataFinished = false;
byte[] handle = null;
try {
using (FileStream fileStream = new FileStream(localPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
if (progressDelegate != null) {
progressDelegate(SFTPFileTransferStatus.Open, transmitted);
}
handle = OpenFile(requestId, remotePath, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
var dataToSend = new AtomicBox<DataFragment>();
var cancelTask = new CancellationTokenSource();
var cancelToken = cancelTask.Token;
Task readFileTask = Task.Run(() => {
// SSH_FXP_WRITE header part
// 4 bytes : packet length
// 1 byte : message type (SSH_FXP_WRITE)
// 4 bytes : request id
// 4 bytes : handle length
// n bytes : handle
// 8 bytes : offset
// 4 bytes : length of the datagram
int buffSize = _channel.MaxChannelDatagramSize - 25 - handle.Length;
// use multiple buffers cyclically.
// at least 3 buffers are required.
DataFragment[] dataFrags =
{
new DataFragment(new byte[buffSize], 0, buffSize),
new DataFragment(new byte[buffSize], 0, buffSize),
new DataFragment(new byte[buffSize], 0, buffSize),
};
int buffIndex = 0;
while (true) {
if (cancelToken.IsCancellationRequested) {
return;
}
DataFragment df = dataFrags[buffIndex];
buffIndex = (buffIndex + 1) % 3;
int length = fileStream.Read(df.Data, 0, df.Data.Length);
if (length == 0) {
df = null; // end of file
}
else {
df.SetLength(0, length);
}
// pass to the sending loop
while (true) {
if (dataToSend.TrySet(df, 500)) {
break;
}
if (cancelToken.IsCancellationRequested) {
return;
}
}
if (length == 0) {
return; // end of file
}
}
}, cancelToken);
try {
while (true) {
if (cancellation != null && cancellation.IsRequested) {
break;
}
DataFragment dataFrag = null;
if (!dataToSend.TryGet(ref dataFrag, 1000)) {
throw new Exception("read error");
}
if (dataFrag == null) {
dataFinished = true;
break;
}
WriteFile(requestId, handle, transmitted, dataFrag.Data, dataFrag.Length);
transmitted += (ulong)dataFrag.Length;
if (progressDelegate != null) {
progressDelegate(SFTPFileTransferStatus.Transmitting, transmitted);
}
}
}
finally {
if (!readFileTask.IsCompleted) {
cancelTask.Cancel();
}
readFileTask.Wait();
}
} // using
}
catch (Exception e) {
if (e is AggregateException) {
pendingException = ((AggregateException)e).InnerExceptions[0];
}
else {
pendingException = e;
}
hasError = true;
}
try {
if (handle != null) {
if (progressDelegate != null)
progressDelegate(SFTPFileTransferStatus.Close, transmitted);
CloseHandle(requestId, handle);
}
if (progressDelegate != null) {
SFTPFileTransferStatus status =
hasError ? SFTPFileTransferStatus.CompletedError :
dataFinished ? SFTPFileTransferStatus.CompletedSuccess : SFTPFileTransferStatus.CompletedAbort;
progressDelegate(status, transmitted);
}
}
catch (Exception) {
if (progressDelegate != null) {
progressDelegate(SFTPFileTransferStatus.CompletedError, transmitted);
}
throw;
}
if (pendingException != null) {
throw new SFTPClientException(pendingException.Message, pendingException);
}
}