//****************************************************************************************************** // FtpFileTransferer.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 05/22/2003 - J. Ritchie Carroll // Generated original version of source code. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 12/09/2009 - Pinal C. Patel // Modified StartTransfer() to open local file in read-only mode for uploads. // 12/17/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** #region [ Contributor License Agreements ] //******************************************************************************************************* // // Code based on the following project: // http://www.codeproject.com/KB/IP/net_ftp_upload.aspx // // Copyright Alex Kwok & Uwe Keim // // The Code Project Open License (CPOL): // http://www.codeproject.com/info/cpol10.aspx // //******************************************************************************************************* #endregion using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading; using GSF.IO; namespace GSF.Net.Ftp { #region [ Enumerations ] /// /// FTP file transfer direction enumeration. /// public enum TransferDirection { /// /// FTP transfer direction set to upload. /// Upload, /// /// FTP transfer direction set to download. /// Download } #endregion // Internal FTP file transfer class internal class FtpFileTransferer { #region [ Members ] // Delegates private delegate void FileCommandDelegate(string remoteFileName); private delegate void StreamCopyDelegate(Stream remote, Stream local); // Fields private readonly StreamCopyDelegate m_streamCopyRoutine; private readonly FileCommandDelegate m_ftpFileCommandRoutine; private readonly FtpSessionConnected m_session; #endregion #region [ Constructors ] internal FtpFileTransferer(FtpDirectory transferStarter, string localFile, string remoteFile, long totalBytes, TransferDirection dir) { TransferDirection = dir; m_session = transferStarter.Session; LocalFileName = localFile; RemoteFileName = remoteFile; TotalBytes = totalBytes; if (dir == TransferDirection.Upload) { m_streamCopyRoutine = LocalToRemote; m_ftpFileCommandRoutine = m_session.ControlChannel.STOR; } else { m_streamCopyRoutine = RemoteToLocal; m_ftpFileCommandRoutine = m_session.ControlChannel.RETR; } } #endregion #region [ Properties ] public string LocalFileName { get; } public string RemoteFileName { get; } public long TotalBytes { get; } public long TotalBytesTransfered { get; private set; } public TransferDirection TransferDirection { get; } public FtpAsyncResult TransferResult { get; private set; } public int TransferedPercentage { get; private set; } #endregion #region [ Methods ] private void TransferThreadProc() { try { StartTransfer(); TransferResult = new FtpAsyncResult("Success.", FtpAsyncResult.Complete); m_session.Host.OnFileTransferNotification(TransferResult); } catch (FtpExceptionBase e) { TransferResult = new FtpAsyncResult($"Transfer fail: {e.Message}", FtpAsyncResult.Fail); m_session.Host.OnFileTransferNotification(TransferResult); } } [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] internal void StartTransfer() { FileStream localStream = null; FtpDataStream remoteStream = null; try { // Files just created may still have a file lock, we'll wait a few seconds for read access if needed... if (TransferDirection == TransferDirection.Upload) FilePath.WaitForReadLock(LocalFileName, m_session.Host.WaitLockTimeout); m_session.Host.OnBeginFileTransfer(LocalFileName, RemoteFileName, TransferDirection); remoteStream = m_session.ControlChannel.GetDataStream(TransferDirection); m_ftpFileCommandRoutine(RemoteFileName); if (TransferDirection == TransferDirection.Download) localStream = new FileStream(LocalFileName, FileMode.Create); else localStream = new FileStream(LocalFileName, FileMode.Open, FileAccess.Read); m_streamCopyRoutine(remoteStream, localStream); // Dispose remote stream before testing file transfer result to ensure // we have received the server's response to the file transfer command remoteStream.Dispose(); TestTransferResult(); m_session.Host.OnEndFileTransfer(LocalFileName, RemoteFileName, TransferDirection); } catch { m_session.Host.OnEndFileTransfer(LocalFileName, RemoteFileName, TransferDirection); throw; } finally { // Need to make sure we end data transfer on the session, which would // normally happen automatically when the remote stream is closed if (remoteStream != null) remoteStream.Dispose(); else m_session.EndDataTransfer(); localStream?.Dispose(); } } internal void StartAsyncTransfer() { Thread thread = new Thread(TransferThreadProc) { Name = $"Transfer file thread: {RemoteFileName}" }; thread.Start(); } private void TestTransferResult() { int responseCode = m_session.ControlChannel.LastResponse.Code; switch (responseCode) { case FtpResponse.ClosingDataChannel: case FtpResponse.RequestFileActionComplete: return; default: throw new FtpDataTransferException("Failed to transfer file.", m_session.ControlChannel.LastResponse); } } private void RemoteToLocal(Stream remote, Stream local) { StreamCopy(local, remote); } private void LocalToRemote(Stream remote, Stream local) { StreamCopy(remote, local); } private void StreamCopy(Stream dest, Stream source) { byte[] buffer = new byte[4 * 1024 + 1]; ProcessProgress progress = new ProcessProgress($"FTP {TransferDirection} File Transfer", $"Transferring file \"{RemoteFileName}\"...", TotalBytes, 0); long onePercentage = TotalBytes / 100; long bytesReadFromLastProgressEvent = 0; int byteRead = source.Read(buffer, 0, 4 * 1024); while (byteRead != 0) { TotalBytesTransfered += byteRead; bytesReadFromLastProgressEvent += byteRead; if (bytesReadFromLastProgressEvent > onePercentage) { TransferedPercentage = (int)(TotalBytesTransfered / (float)TotalBytes * 100); progress.Complete = TotalBytesTransfered; m_session.Host.OnFileTransferProgress(progress, TransferDirection); bytesReadFromLastProgressEvent = 0; } dest.Write(buffer, 0, byteRead); byteRead = source.Read(buffer, 0, 4 * 1024); } } #endregion } }