AsperaFaspService
                    class AsperaFaspService
                
                using Aspera.Transfer;
using Relativity.Transfer.Aspera.Resources;
using Relativity.Transfer.Resources;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
namespace Relativity.Transfer.Aspera
{
    internal class AsperaFaspService
    {
        private static readonly object SyncRoot = new object();
        private readonly ITransferLog log;
        private readonly AsperaClientConfiguration configuration;
        private readonly IAsperaFaspProxy faspProxy;
        private readonly IFileSystemService fileSystemService;
        private int retryCount;
        public int RetryCount {
            get {
                lock (SyncRoot) {
                    return retryCount;
                }
            }
            set {
                lock (SyncRoot) {
                    retryCount = value;
                }
            }
        }
        public AsperaFaspService(ITransferLog log, AsperaClientConfiguration configuration, IAsperaFaspProxy proxy, IFileSystemService fileSystemService)
        {
            if (log == null)
                throw new ArgumentNullException("log");
            if (configuration == null)
                throw new ArgumentNullException("configuration");
            if (proxy == null)
                throw new ArgumentNullException("proxy");
            if (fileSystemService == null)
                throw new ArgumentNullException("fileSystemService");
            this.log = log;
            this.configuration = configuration;
            faspProxy = proxy;
            this.fileSystemService = fileSystemService;
        }
        public static string EncodeCookieString(string name, Guid cookie)
        {
            if (string.IsNullOrWhiteSpace(name))
                return cookie.ToString();
            return $"""{cookie}""{name}";
        }
        public static string EncodeCookieString(string name, Guid jobId, Guid cookie)
        {
            return $"{EncodeCookieString(name, cookie)}""{jobId}";
        }
        public Guid CreateAsperaJob(ITransferRequest request, FileTransferListener listener)
        {
            if (request == null)
                throw new ArgumentNullException("request");
            if (listener == null)
                throw new ArgumentNullException("listener");
            faspProxy.SetupRuntime();
            if (!request.ClientRequestId.HasValue)
                request.ClientRequestId = Guid.NewGuid();
            if (!request.JobId.HasValue)
                request.JobId = Guid.NewGuid();
            FileLocation source;
            FileLocation destination;
            if (request.Direction == TransferDirection.Upload) {
                source = new LocalFileLocation();
                destination = CreateRemoteFileLocation(new TransferPath[0]);
            } else {
                source = CreateRemoteFileLocation(new TransferPath[0]);
                destination = new LocalFileLocation();
            }
            XferParams xferParams = CreateTransferParameters(request.Name, request.JobId.Value, request.ClientRequestId.Value);
            JobOrder order = new JobOrder(source, destination, xferParams);
            return faspProxy.StartTransfer(order, request.JobId.Value, RetryCount, listener);
        }
        public void LockSession(Guid sessionId)
        {
            if (sessionId == Guid.Empty)
                throw new ArgumentException(AsperaStrings.SessionIdArgumentExceptionMessage, "sessionId");
            faspProxy.LockPersistentSession(sessionId);
        }
        public void AddFile(Guid sessionId, TransferPath path)
        {
            if (sessionId == Guid.Empty)
                throw new ArgumentException(AsperaStrings.SessionIdArgumentExceptionMessage, "sessionId");
            if (path == (TransferPath)null)
                throw new ArgumentNullException("path");
            if (string.IsNullOrEmpty(path.SourcePath))
                throw new ArgumentException(CoreStrings.SourcePathArgumentExceptionMessage, "path");
            if (string.IsNullOrEmpty(path.TargetPath))
                throw new ArgumentException(CoreStrings.TargetPathArgumentExceptionMessage, "path");
            try {
                faspProxy.AddPersistentPath(sessionId, path);
            } catch (FaspManagerException innerException) {
                throw new TransferException(AsperaStrings.AddSourceExcpetionMessage, innerException);
            }
        }
        public bool JobExists(Guid sessionId)
        {
            if (sessionId == Guid.Empty)
                throw new ArgumentException(AsperaStrings.SessionIdArgumentExceptionMessage, "sessionId");
            return faspProxy.GetSessionIds().Any((Guid x) => x == sessionId);
        }
        public void CancelJob(Guid sessionId, bool persist, FileTransferListener listener)
        {
            if (sessionId == Guid.Empty)
                throw new ArgumentException(AsperaStrings.SessionIdArgumentExceptionMessage, "sessionId");
            if (!faspProxy.GetSessionIds().Any((Guid x) => x == sessionId))
                log.LogInformation("The Aspera '{SessionId}' session cannot be cancelled because the job does not exist.", sessionId);
            else {
                log.LogInformation("Preparing to cancel the '{SessionId}' Aspera session.", sessionId);
                faspProxy.RemoveJobListener(sessionId, listener);
                faspProxy.CancelTransfer(sessionId, persist);
                faspProxy.RemoveSession(sessionId);
                log.LogInformation("Successfully cancelled the '{SessionId}' Aspera session.", sessionId);
            }
        }
        public void RemoveJob(Guid sessionId, FileTransferListener listener)
        {
            if (sessionId == Guid.Empty)
                throw new ArgumentException(AsperaStrings.SessionIdArgumentExceptionMessage, "sessionId");
            if (faspProxy.GetSessionIds().Any((Guid x) => x == sessionId))
                try {
                    faspProxy.RemoveJobListener(sessionId, listener);
                } finally {
                    faspProxy.RemoveSession(sessionId);
                }
            else
                log.LogInformation("The Aspera '{SessionId}' job cannot be removed because the job does not exist.", sessionId);
        }
        private RemoteFileLocation CreateRemoteFileLocation(IEnumerable<TransferPath> paths)
        {
            RemoteFileLocation remoteFileLocation;
            try {
                AsperaCredential asperaCredential = new AsperaCredential(configuration.Credential);
                if (!string.IsNullOrEmpty(configuration.SshPrivateKeyFile)) {
                    if (!fileSystemService.FileExists(configuration.SshPrivateKeyFile))
                        throw new TransferException(string.Format(CultureInfo.CurrentCulture, AsperaStrings.AsperaSshPrivateKeyFileNotFoundArgumentExceptionMessage, configuration.SshPrivateKeyFile), true);
                    if (string.IsNullOrEmpty(configuration.SecurityToken))
                        throw new TransferException(string.Format(CultureInfo.CurrentCulture, AsperaStrings.AsperaSecurityTokenArgumentExceptionMessage, Array.Empty<object>()), true);
                    remoteFileLocation = new RemoteFileLocation(asperaCredential.Host.ToString(), asperaCredential.AccountUserName.UnprotectData(), configuration.SshPrivateKeyFile, configuration.SshPassphrase);
                } else
                    remoteFileLocation = new RemoteFileLocation(asperaCredential.Host.ToString(), asperaCredential.AccountUserName.UnprotectData(), asperaCredential.AccountPassword.UnprotectData());
            } catch (Exception ex) {
                throw new TransferException(ex.Message, true);
            }
            foreach (TransferPath path in paths) {
                if (string.IsNullOrEmpty(path.SourcePath))
                    throw new ArgumentException(AsperaStrings.AsperaTargetPathArgumentExceptionMessage, "paths");
                if (string.IsNullOrEmpty(path.TargetFileName))
                    remoteFileLocation.addPath(path.SourcePath);
                else
                    remoteFileLocation.addPath(path.SourcePath, path.TargetFileName);
            }
            return remoteFileLocation;
        }
        private XferParams CreateTransferParameters(string name, Guid jobId, Guid cookie)
        {
            string localLogDir = TransferHelper.CreateTransferLogDirectory(log, fileSystemService, configuration.TransferLogDirectory, "Aspera-Transfers");
            string token = string.Empty;
            if (!string.IsNullOrEmpty(configuration.SecurityToken))
                token = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(configuration.SecurityToken));
            XferParams xferParams = new XferParams {
                reportSkippedFiles = configuration.ReportNotOverriddenFiles,
                cookie = EncodeCookieString(name, jobId, cookie),
                createDirs = configuration.CreateDirectories,
                cipher = new Cipher?(TypeHelper.ParseEnum(configuration.EncryptionCipher, Cipher.AES_256, log)),
                httpFallback = null,
                localLogDir = localLogDir,
                minimumRateKbps = configuration.MinDataRateMbps * 1000,
                overwritePolicy = TypeHelper.ParseEnum(configuration.OverwritePolicy, OverwritePolicy.ALWAYS, log),
                partialFileSuffix = null,
                persist = true,
                policy = TypeHelper.ParseEnum(configuration.Policy, Policy.FAIR, log),
                preserveDates = configuration.PreserveDates,
                resumeCheck = new Resume?(TypeHelper.ParseEnum(configuration.ResumeCheck, Resume.OFF, log)),
                retryTimeoutS = Convert.ToInt32(configuration.HttpTimeoutSeconds),
                saveBeforeOverwriteEnabled = configuration.SaveBeforeOverwriteEnabled,
                targetRateKbps = configuration.TargetDataRateMbps * 1000,
                token = token,
                tcpPort = configuration.TcpPort,
                udpPort = configuration.UdpPortStartRange,
                datagramSize = configuration.DatagramSize
            };
            log.LogInformation(xferParams.Dump(), Array.Empty<object>());
            return xferParams;
        }
    }
}