guibranco/CrispyWaffle

View on GitHub
Src/CrispyWaffle.Utils/Communications/FtpClient.cs

Summary

Maintainability
B
4 hrs
Test Coverage
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using CrispyWaffle.Configuration;
using CrispyWaffle.Extensions;
using CrispyWaffle.Log;
using CrispyWaffle.Utils.GoodPractices;

namespace CrispyWaffle.Utils.Communications
{
    /// <summary>
    /// Class FtpClient.
    /// </summary>
    public class FtpClient
    {
        /// <summary>
        /// The synchronize root.
        /// </summary>
        private readonly object _syncRoot = new();

        /// <summary>
        /// The host.
        /// </summary>
        private readonly string _host;

        /// <summary>
        /// The port.
        /// </summary>
        private readonly int _port;

        /// <summary>
        /// The username.
        /// </summary>
        private readonly string _userName;

        /// <summary>
        /// The password.
        /// </summary>
        private readonly string _password;

        /// <summary>
        /// The directory.
        /// </summary>
        private readonly string _remoteDirectory;

        /// <summary>
        /// The files.
        /// </summary>
        private readonly Queue<string> _files = new();

        /// <summary>
        /// Initializes a new instance of the <see cref="FtpClient"/> class.
        /// </summary>
        /// <param name="ftp">The FtpClient.</param>
        /// <param name="remoteDirectory">The remote directory.</param>
        public FtpClient(IConnection ftp, string remoteDirectory)
            : this(
                ftp?.Host,
                ftp?.Port ?? 0,
                ftp?.Credentials.Username,
                ftp?.Credentials.Password,
                remoteDirectory
            ) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="FtpClient"/> class.
        /// </summary>
        /// <param name="host">The host.</param>
        /// <param name="port">The port.</param>
        /// <param name="userName">Name of the user.</param>
        /// <param name="password">The password.</param>
        /// <param name="remoteDirectory">The directory.</param>
        /// <exception cref="System.ArgumentNullException">remoteDirectory.</exception>
        public FtpClient(
            string host,
            int port,
            string userName,
            string password,
            string remoteDirectory
        )
        {
            if (string.IsNullOrWhiteSpace(remoteDirectory))
            {
                throw new ArgumentNullException(nameof(remoteDirectory));
            }

            _host = host;
            _port = port;
            _userName = userName;
            _password = password;
            _remoteDirectory = remoteDirectory;
            if (_remoteDirectory.EndsWith(@"/", StringComparison.InvariantCultureIgnoreCase))
            {
                _remoteDirectory = _remoteDirectory.Substring(0, _remoteDirectory.Length - 1);
            }
        }

        /// <summary>
        /// Check if the path/file exists in the FtpClient host.
        /// </summary>
        /// <param name="path">The path String.</param>
        /// <returns>true if it succeeds, false if it fails.</returns>
        /// <exception cref="System.InvalidOperationException">Response stream is null.</exception>
        private bool ExistsInternal(string path)
        {
            try
            {
                LogConsumer.Info(
                    "Checking in FtpClient the path/file: {0}",
                    path.GetPathOrFileName()
                );
                var uri = new Uri(path);
                var request = (FtpWebRequest)WebRequest.Create(uri);
                request.Credentials = new NetworkCredential(_userName, _password);
                request.Method = string.IsNullOrWhiteSpace(uri.GetFileExtension())
                    ? WebRequestMethods.Ftp.ListDirectory
                    : WebRequestMethods.Ftp.GetFileSize;
                request.Timeout = 30000;
                request.ReadWriteTimeout = 90000;
                request.UsePassive = true;

                using var response = (FtpWebResponse)request.GetResponse();
                using (var responseStream = response.GetResponseStream())
                {
                    using var reader = new StreamReader(
                        responseStream
                            ?? throw new InvalidOperationException("Null response stream")
                    );
                    while (!reader.EndOfStream)
                    {
                        _files.Enqueue(reader.ReadLine());
                    }
                }

                return string.IsNullOrWhiteSpace(uri.GetFileExtension())
                    ? response.StatusCode == FtpStatusCode.OpeningData
                    : response.StatusCode == FtpStatusCode.FileStatus
                        || response.StatusCode == FtpStatusCode.OpeningData;
            }
            catch (WebException)
            {
                return false;
            }
        }

        /// <summary>
        /// Creates the file in the FtpClient host.
        /// </summary>
        /// <param name="path">The path String.</param>
        /// <param name="bytes">The bytes.</param>
        /// <returns>true if it succeeds, false if it fails.</returns>
        /// <exception cref="CrispyWaffle.Utils.GoodPractices.FtpClientException">create.</exception>
        private bool CreateInternal(string path, byte[] bytes)
        {
            var result = false;
            try
            {
                LogConsumer.Info("Uploading to FtpClient the file: {0}", path.GetPathOrFileName());
                var uri = new Uri(path);
                var request = (FtpWebRequest)WebRequest.Create(uri);
                request.Credentials = new NetworkCredential(_userName, _password);
                request.UsePassive = true;
                if (!string.IsNullOrWhiteSpace(uri.GetFileExtension()))
                {
                    request.Method = WebRequestMethods.Ftp.UploadFile;
                    request.ContentLength = bytes.Length;
                    var stream = request.GetRequestStream();
                    stream.Write(bytes, 0, bytes.Length);
                    stream.Close();
                }
                else
                {
                    request.Method = WebRequestMethods.Ftp.MakeDirectory;
                }

                var response = (FtpWebResponse)request.GetResponse();
                if (
                    !string.IsNullOrWhiteSpace(uri.GetFileExtension())
                        && response.StatusCode == FtpStatusCode.ClosingData
                    || response.StatusCode == FtpStatusCode.PathnameCreated
                )
                {
                    result = true;
                }

                response.Close();
            }
            catch (WebException e)
            {
                throw new FtpClientException(path.GetPathOrFileName(), "create", e);
            }

            return result;
        }

        /// <summary>
        /// Removes the path/file in the FtpClient host.
        /// </summary>
        /// <param name="path">The path String.</param>
        /// <returns>true if it succeeds, false if it fails.</returns>
        /// <exception cref="CrispyWaffle.Utils.GoodPractices.FtpClientException">remove.</exception>
        private void RemoveInternal(string path)
        {
            try
            {
                LogConsumer.Info("Uploading to FtpClient the file: {0}", path.GetPathOrFileName());
                var fullPath = new Uri(path);
                var request = (FtpWebRequest)WebRequest.Create(fullPath);
                request.Credentials = new NetworkCredential(_userName, _password);
                request.Method = !string.IsNullOrWhiteSpace(fullPath.GetFileExtension())
                    ? WebRequestMethods.Ftp.DeleteFile
                    : WebRequestMethods.Ftp.RemoveDirectory;
                request.UsePassive = true;
                var response = (FtpWebResponse)request.GetResponse();
                if (response.StatusCode != FtpStatusCode.FileActionOK)
                {
                    throw new FtpClientException(
                        path.GetPathOrFileName(),
                        "remove",
                        response.StatusCode
                    );
                }
            }
            catch (WebException e)
            {
                throw new FtpClientException(path.GetPathOrFileName(), "remove", e);
            }
        }

        /// <summary>
        /// Gets the FtpClient URL.
        /// </summary>
        /// <returns>StringBuilder.</returns>
        private StringBuilder GetFtpUrl()
        {
            var str = new StringBuilder();
            return str.Append(@"ftp://")
                .Append(_host)
                .Append(@":")
                .Append(_port)
                .Append(@"/")
                .Append(_remoteDirectory)
                .Append(@"/");
        }

        /// <summary>
        /// Check if a file or directory exists in the FtpClient endpoint.
        /// </summary>
        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
        private bool Exists() => ExistsInternal(GetFtpUrl().ToString());

        /// <summary>
        /// Check if the path exists in the FtpClient endpoint
        /// </summary>
        /// <param name="path">The path.</param>
        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
        private bool Exists(string path) => ExistsInternal(GetFtpUrl().Append(path).ToString());

        /// <summary>
        /// Removes this instance.
        /// </summary>
        private void Remove() => RemoveInternal(GetFtpUrl().ToString());

        /// <summary>
        /// Creates the directory.
        /// </summary>
        private void CreateDirectory() => CreateInternal(GetFtpUrl().ToString(), null);

        /// <summary>
        /// Removes the specified path.
        /// </summary>
        /// <param name="path">The path.</param>
        public void Remove(string path)
        {
            lock (_syncRoot)
            {
                RemoveInternal(GetFtpUrl().Append(path).ToString());
            }
        }

        /// <summary>
        /// Creates the directory.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
        public bool CreateDirectory(string name)
        {
            lock (_syncRoot)
            {
                return CreateInternal(GetFtpUrl().Append(name).Append(@"/").ToString(), null);
            }
        }

        /// <summary>
        /// Uploads the specified file name.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="bytes">The bytes.</param>
        /// <returns><c>true</c> if successfully upload the file, <c>false</c> otherwise.</returns>
        /// <exception cref="System.ArgumentNullException">fileName.</exception>
        /// <exception cref="System.ArgumentNullException">bytes.</exception>
        public bool Upload(string fileName, byte[] bytes)
        {
            if (fileName == null)
            {
                throw new ArgumentNullException(nameof(fileName));
            }

            if (bytes == null)
            {
                throw new ArgumentNullException(nameof(bytes));
            }

            lock (_syncRoot)
            {
                if (!Exists())
                {
                    CreateDirectory();
                }

                if (Exists(fileName))
                {
                    Remove(fileName);
                }

                return CreateInternal(GetFtpUrl().Append(fileName).ToString(), bytes);
            }
        }

        /// <summary>
        /// Clears the directory.
        /// </summary>
        public void ClearDirectory()
        {
            lock (_syncRoot)
            {
                _files.Clear();
                if (Exists())
                {
                    var counter = _files.Count;
                    for (var x = 0; x < counter; x++)
                    {
                        Remove(_files.Dequeue());
                    }

                    Remove();
                }

                CreateDirectory();
            }
        }
    }
}