/// <summary> /// Upload a file. /// </summary> /// /// <param name="localPath">Local file path to upload.</param> /// <param name="remotePath">Remote file path to write.</param> /// <param name="cancellation">An object to request the cancellation. Set null if the cancellation is not needed.</param> /// <param name="progressDelegate">Delegate to notify progress. Set null if notification is not needed.</param> /// /// <exception cref="SFTPClientErrorException">Operation failed.</exception> /// <exception cref="SFTPClientTimeoutException">Timeout has occured.</exception> /// <exception cref="SFTPClientInvalidStatusException">Invalid status.</exception> /// <exception cref="SFTPClientException">Error.</exception> /// <exception cref="Exception">Error.</exception> 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); } }
/// <summary> /// Download a file. /// </summary> /// <remarks> /// Even if download failed, local file is not deleted. /// </remarks> /// /// <param name="remotePath">Remote file path to download.</param> /// <param name="localPath">Local file path to save.</param> /// <param name="cancellation">An object to request the cancellation. Set null if the cancellation is not needed.</param> /// <param name="progressDelegate">Delegate to notify progress. Set null if notification is not needed.</param> /// /// <exception cref="SFTPClientErrorException">Operation failed.</exception> /// <exception cref="SFTPClientTimeoutException">Timeout has occured.</exception> /// <exception cref="SFTPClientInvalidStatusException">Invalid status.</exception> /// <exception cref="SFTPClientException">Error.</exception> /// <exception cref="Exception">Error.</exception> public void DownloadFile(string remotePath, string localPath, Cancellation cancellation, SFTPFileTransferProgressDelegate progressDelegate) { CheckStatus(); uint requestId = ++_requestId; ulong transmitted = 0; Exception pendingException = null; if (progressDelegate != null) { progressDelegate(SFTPFileTransferStatus.Open, transmitted); } byte[] handle; try { handle = OpenFile(requestId, remotePath, SSH_FXF_READ); } catch (Exception) { if (progressDelegate != null) { progressDelegate(SFTPFileTransferStatus.CompletedError, transmitted); } throw; } bool hasError = false; bool dataFinished = false; try { using (FileStream fileStream = new FileStream(localPath, FileMode.Create, FileAccess.Write, FileShare.Read)) { var dataToSave = new AtomicBox<DataFragment>(); var cancelTask = new CancellationTokenSource(); var cancelToken = cancelTask.Token; Task writeFileTask = Task.Run(() => { while (true) { DataFragment df = null; while (true) { if (dataToSave.TryGet(ref df, 500)) { break; } if (cancelToken.IsCancellationRequested) { return; } } if (df == null) { dataFinished = true; return; // end of file } fileStream.Write(df.Data, df.Offset, df.Length); } }, cancelToken); try { // fixed buffer size is used. // it is very difficult to decide optimal buffer size // because the server may change the packet size // depending on the available window size. const int buffSize = 0x10000; // 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 (cancellation != null && cancellation.IsRequested) { break; } DataFragment df = dataFrags[buffIndex]; buffIndex = (buffIndex + 1) % 3; int length = ReadFile(requestId, handle, transmitted, buffSize, df.Data); if (length == 0) { df = null; // end of file } else { df.SetLength(0, length); } // pass to the writing task if (!dataToSave.TrySet(df, 1000)) { throw new Exception("write error"); } transmitted += (ulong)length; if (progressDelegate != null) { progressDelegate(SFTPFileTransferStatus.Transmitting, transmitted); } if (length == 0) { writeFileTask.Wait(1000); break; // EOF } } } finally { if (!writeFileTask.IsCompleted) { cancelTask.Cancel(); } writeFileTask.Wait(); } } } catch (Exception e) { if (e is AggregateException) { pendingException = ((AggregateException)e).InnerExceptions[0]; } else { pendingException = e; } hasError = true; } try { 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); } }