<PackageReference Include="Relativity.Server.Import.SDK" Version="2.9.2" />

TapiBridgeBase2

public abstract class TapiBridgeBase2 : ITapiBridge, IDisposable
Represents a class object to provide a bridge from the Transfer API to existing Import/Export code.
using Polly; using Relativity.DataExchange.Io; using Relativity.DataExchange.Logger; using Relativity.DataExchange.Resources; using Relativity.Logging; using Relativity.Transfer; using Relativity.Transfer.Aspera; using Relativity.Transfer.FileShare; using Relativity.Transfer.Http; using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Relativity.DataExchange.Transfer { public abstract class TapiBridgeBase2 : ITapiBridge, IDisposable { private readonly object syncRoot = new object(); private readonly ITapiObjectService tapiObjectService; private readonly CancellationToken cancellationToken; private readonly TransferContext transferContext; private readonly TapiBridgeParameters2 parameters; private readonly List<TapiListenerBase> transferListeners = new List<TapiListenerBase>(); private readonly TransferDirection currentDirection; private readonly TapiTotals jobTotals = new TapiTotals(); private readonly TapiTotals batchTotals = new TapiTotals(); private readonly bool useLegacyWebApi; private Guid? currentJobId; private int currentJobNumber; private double maxInactivitySeconds; private ITransferRequest jobRequest; private bool raisedPermissionIssue; private string raisedPermissionIssueMessage; private IRelativityTransferHost relativityTransferHost; private ITransferClient transferClient; private ITransferJob transferJob; private DateTime transferActivityTimestamp; private bool disposed; public TapiClient Client => tapiObjectService.GetTapiClient(ClientId); public Guid ClientId => transferClient?.Id ?? Guid.Empty; public Guid InstanceId { get; } public TapiTotals JobTotals => jobTotals; public TapiBridgeParameters2 Parameters => parameters; public string TargetPath { get; set; } public bool TransfersPending { get { if (TransferJob != null) return TransferJob.JobService.RequestTransferPathCount > 0; return false; } } public int WorkspaceId => parameters.WorkspaceId; protected bool RaisedPermissionIssue { get { lock (syncRoot) { return raisedPermissionIssue; } } private set { lock (syncRoot) { raisedPermissionIssue = value; } } } protected string RaisedPermissionIssueMessage { get { lock (syncRoot) { return raisedPermissionIssueMessage; } } private set { lock (syncRoot) { raisedPermissionIssueMessage = value; } } } protected ITransferJob TransferJob { get { lock (syncRoot) { return transferJob; } } private set { lock (syncRoot) { transferJob = value; } } } protected ILog Logger { get; } protected ITransferLog TransferLog { get; set; } private string ClientDisplayName => transferClient?.DisplayName ?? Strings.ClientInitializing; public event EventHandler<TapiMessageEventArgs> TapiStatusMessage; public event EventHandler<TapiMessageEventArgs> TapiErrorMessage; public event EventHandler<TapiMessageEventArgs> TapiWarningMessage; public event EventHandler<TapiClientEventArgs> TapiClientChanged; public event EventHandler<TapiProgressEventArgs> TapiProgress; public event EventHandler<TapiLargeFileProgressEventArgs> TapiLargeFileProgress; public event EventHandler<TapiStatisticsEventArgs> TapiStatistics; public event EventHandler<TapiMessageEventArgs> TapiFatalError; internal TapiBridgeBase2(ITapiObjectService service, TapiBridgeParameters2 parameters, TransferDirection direction, TransferContext context, ILog logger, bool useLegacyWebApi, CancellationToken token) { if (service == null) throw new ArgumentNullException("service"); if (parameters == null) throw new ArgumentNullException("parameters"); if (parameters.Credentials == null) throw new ArgumentException("The credentials information must be specified.", "parameters"); if (parameters.WorkspaceId < 1) throw new ArgumentOutOfRangeException("parameters", Strings.WorkspaceExceptionMessage); if (parameters.WebCookieContainer == null) parameters.WebCookieContainer = new CookieContainer(); if (context == null) context = CreateDefaultTransferContext(parameters); InstanceId = Guid.NewGuid(); tapiObjectService = service; currentDirection = direction; this.parameters = parameters; TargetPath = parameters.TargetPath; cancellationToken = token; Logger = (((object)logger) ?? ((object)new NullLogger())); TransferLog = new RelativityTransferLog(Logger); currentJobNumber = 0; transferContext = context; SetupTransferListeners(); UpdateAllTransferListenersClientName(); this.useLegacyWebApi = useLegacyWebApi; } public string AddPath(TransferPath path) { if (path == (TransferPath)null) throw new ArgumentNullException("path"); ValidateTransferPath(path); CheckDispose(); CreateTransferJob(false, false); if (TransferJob != null) try { Exception transferException = null; return RetrySyntax.Retry(Policy.Handle<TransferException>(), 3, (Action<Exception, int>)delegate(Exception exception, int count) { transferException = exception; if (TransferJob?.JobService.Statistics != null && TransferJob.JobService.Statistics.JobError) Logger.LogError(exception, "Failed to add a path to the {TransferJobId} transfer job due to a job-level error. Job error: {JobErrorMessage}", new object[2] { jobRequest?.JobId, TransferJob.JobService.Statistics.JobErrorMessage }); else Logger.LogError(exception, "Failed to add a path to the {TransferJobId} transfer job.", new object[1] { jobRequest?.JobId }); SwitchTapiClientMode(exception, (count >= 3) ? TapiClient.Web : Client); }).Execute<string>((Func<string>)delegate { if (transferException == null || !GetIsTransferPathInJobQueue(path)) try { TransferJob.AddPath(path, cancellationToken); IncrementTotalFileTransferRequests(); } catch { if (GetIsTransferPathInJobQueue(path)) IncrementTotalFileTransferRequests(); throw; } if (string.IsNullOrEmpty(path.TargetFileName)) return FileSystem.Instance.Path.GetFileName(path.SourcePath); return path.TargetFileName; }); } catch (ArgumentException ex) { Logger.LogWarning((Exception)ex, "There was a problem adding the '{SourceFile}' source file to the {TransferJobId} transfer job.", new object[2] { path.SourcePath.Secure(), jobRequest?.JobId }); throw new FileNotFoundException(ex.Message, path.SourcePath); } catch (FileNotFoundException ex2) { Logger.LogWarning((Exception)ex2, "The '{SourceFile}' source file doesn't exist.", new object[1] { path.SourcePath.Secure() }); throw; } catch (OperationCanceledException) { LogCancelRequest(); return (!string.IsNullOrEmpty(path.TargetFileName)) ? path.TargetFileName : FileSystem.Instance.Path.GetFileName(path.SourcePath); } throw new InvalidOperationException(Strings.TransferJobNullExceptionMessage); } public virtual void CreateTransferClient() { CheckDispose(); if (transferClient == null) { ClientConfiguration clientConfiguration = CreateClientConfiguration(); maxInactivitySeconds = (double)parameters.MaxInactivitySeconds; if (maxInactivitySeconds < 0) maxInactivitySeconds = 1.25 * (double)(parameters.WaitTimeBetweenRetryAttempts * (parameters.MaxJobRetryAttempts + 1)); try { Guid clientId = tapiObjectService.GetClientId(parameters); if (clientId != Guid.Empty) { clientConfiguration.ClientId = clientId; CreateTransferClient(clientConfiguration); PublishClientChanged(ClientChangeReason.ForceConfig); } else { clientConfiguration.ClientId = Guid.Empty; TransferClientStrategy val; if (!string.IsNullOrEmpty(parameters.ForceClientCandidates)) { val = new TransferClientStrategy(parameters.ForceClientCandidates); Logger.LogInformation("Overriding the default transfer client strategy. Candidates={ForceClientCandidates}", new object[1] { parameters.ForceClientCandidates }); } else { val = new TransferClientStrategy(); Logger.LogInformation("Using the default transfer client strategy.", Array.Empty<object>()); } Logger.LogInformation("TAPI client configuration {Configuration}", new object[1] { clientConfiguration }); IRelativityTransferHost val2 = CreateTransferHost(); transferClient = val2.CreateClientAsync(clientConfiguration, (ITransferClientStrategy)val, cancellationToken).GetAwaiter().GetResult(); Logger.LogInformation("TAPI created the {Client} client via best-fit strategy.", new object[1] { transferClient.DisplayName }); PublishClientChanged(ClientChangeReason.BestFit); } } catch (Exception ex) { Logger.LogError(ex, "The transfer client construction failed.", Array.Empty<object>()); clientConfiguration.ClientId = new Guid("18F9E382-288C-4B6D-8AE8-FBA4D88CD841"); CreateTransferClient(clientConfiguration); PublishClientChanged(ClientChangeReason.HttpFallback); } finally { OptimizeClient(); } } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public virtual void LogTransferParameters() { Version version = GetType().Assembly.GetName().Version; Version version2 = typeof(ITransferClient).Assembly.GetName().Version; Logger.LogInformation("Import/Export Core - Version: {ImportExportCoreVersion}", new object[1] { version }); Logger.LogInformation("TAPI - Version: {TapiVersion}", new object[1] { version2 }); Logger.LogInformation("Application: {Application}", new object[1] { parameters.Application }); Logger.LogInformation("Client request id: {ClientRequestId}", new object[1] { parameters.ClientRequestId }); Logger.LogInformation("Aspera doc root level: {AsperaDocRootLevels}", new object[1] { parameters.AsperaDocRootLevels }); Logger.LogInformation("Aspera datagram size: {AsperaDatagramSize}", new object[1] { parameters.AsperaDatagramSize }); Logger.LogInformation("File share: {FileShare}", new object[1] { parameters.FileShare.Secure() }); Logger.LogInformation("Force Aspera client: {ForceAsperaClient}", new object[1] { parameters.ForceAsperaClient }); Logger.LogInformation("Force Fileshare client: {ForceFileShareClient}", new object[1] { parameters.ForceFileShareClient }); Logger.LogInformation("Force HTTP client: {ForceHttpClient}", new object[1] { parameters.ForceHttpClient }); Logger.LogInformation("Force client candidates: {ForceClientCandidates}", new object[1] { parameters.ForceClientCandidates }); Logger.LogInformation("HTTP timeout: {HttpTimeoutSeconds} seconds", new object[1] { parameters.TimeoutSeconds }); Logger.LogInformation("Max inactivity seconds: {MaxInactivitySeconds}", new object[1] { parameters.MaxInactivitySeconds }); Logger.LogInformation("Max job parallelism: {MaxJobParallelism}", new object[1] { parameters.MaxJobParallelism }); Logger.LogInformation("Max job retry attempts: {MaxJobRetryAttempts}", new object[1] { parameters.MaxJobRetryAttempts }); Logger.LogInformation("Min data rate: {MinDataRateMbps} Mbps", new object[1] { parameters.MinDataRateMbps }); Logger.LogInformation("Preserve file timestamps: {PreserveFileTimestamps}", new object[1] { parameters.PreserveFileTimestamps }); Logger.LogInformation("Retry on file permission error: {PermissionErrorsRetry}", new object[1] { parameters.PermissionErrorsRetry }); Logger.LogInformation("Retry on bad path error: {BadPathErrorsRetry}", new object[1] { parameters.BadPathErrorsRetry.Secure() }); Logger.LogInformation("Submit APM metrics: {SubmitApmMetrics}", new object[1] { parameters.SubmitApmMetrics }); Logger.LogInformation("Target data rate: {TargetDataRateMbps} Mbps", new object[1] { parameters.TargetDataRateMbps }); Logger.LogInformation("Wait time between retry attempts: {WaitTimeBetweenRetryAttempts}", new object[1] { parameters.WaitTimeBetweenRetryAttempts }); Logger.LogInformation("Workspace identifier: {WorkspaceId}", new object[1] { parameters.WorkspaceId }); } public TapiTotals WaitForTransfers(string waitMessage, string successMessage, string errorMessage, bool keepJobAlive) { CheckDispose(); PublishStatusMessage(waitMessage, 0); try { LogTransferTotals("Pre", false); TapiTotals result = (TransferJob != null && (batchTotals.TotalFileTransferRequests != 0 || jobTotals.TotalFileTransferRequests != 0)) ? (keepJobAlive ? WaitForCompletedTransfers() : WaitForCompletedTransferJob()) : (keepJobAlive ? batchTotals.DeepCopy() : jobTotals.DeepCopy()); LogTransferTotals("Post", true); PublishStatusMessage(successMessage, 0); return result; } catch (Exception ex) when (!ex.IsCanceledByUser(cancellationToken)) { PublishWarningMessage(errorMessage, 0); Logger.LogError(ex, errorMessage, Array.Empty<object>()); throw; } } internal void ClearAllTotals() { batchTotals.Clear(); jobTotals.Clear(); } protected static TransferContext CreateDefaultTransferContext(TapiBridgeParameters2 parameters) { if (parameters == null) throw new ArgumentNullException("parameters"); return new TransferContext { StatisticsRateSeconds = 1, LargeFileProgressEnabled = true, LargeFileProgressRateSeconds = 1 }; } protected virtual ClientConfiguration CreateClientConfiguration() { parameters.HttpErrorNumberOfRetries = 1; ClientConfiguration clientConfiguration = new ClientConfiguration { BadPathErrorsRetry = parameters.BadPathErrorsRetry, BcpRootFolder = parameters.AsperaBcpRootFolder, CookieContainer = parameters.WebCookieContainer, Credential = parameters.TransferCredential, FileTransferHint = FileTransferHint.Natives, FileNotFoundErrorsDisabled = parameters.FileNotFoundErrorsDisabled, FileNotFoundErrorsRetry = parameters.FileNotFoundErrorsRetry, HttpTimeoutSeconds = (double)parameters.TimeoutSeconds, MaxJobParallelism = parameters.MaxJobParallelism, MaxJobRetryAttempts = parameters.MaxJobRetryAttempts, MaxHttpRetryAttempts = parameters.HttpErrorNumberOfRetries, MinDataRateMbps = parameters.MinDataRateMbps, PermissionErrorsRetry = parameters.PermissionErrorsRetry, PreserveDates = parameters.PreserveFileTimestamps, SupportCheckPath = parameters.SupportCheckPath, TargetDataRateMbps = parameters.TargetDataRateMbps, TransferLogDirectory = parameters.TransferLogDirectory, ValidateSourcePaths = false, SavingMemoryMode = true, UseLegacyWebApi = useLegacyWebApi }; if (parameters.AsperaDatagramSize != 0) clientConfiguration["aspera-datagram-size"] = parameters.AsperaDatagramSize; return clientConfiguration; } protected abstract TransferRequest CreateTransferRequestForJob(TransferContext context); protected abstract void SetupRemotePathResolvers(ITransferRequest request); protected abstract string TransferFileFatalMessage(); private static string GetTransferErrorMessage(ITransferResult result) { string text = string.Empty; if (result.TransferError != null) text = result.TransferError.Message; if (string.IsNullOrEmpty(text) && result.Issues.Count > 0) { List<ITransferIssue> list = (from x in result.Issues orderby x.Index select x).ToList(); ITransferIssue transferIssue = list.FindLast((ITransferIssue x) => x.Path != (TransferPath)null) ?? list.LastOrDefault(); if (transferIssue != null) text = transferIssue.Message; } if (string.IsNullOrEmpty(text)) text = Strings.TransferJobExceptionMessage; return text; } private void CheckDispose() { if (!disposed) return; throw new ObjectDisposedException(Strings.ObjectDisposedExceptionMessage); } private bool CheckCompletedTransfers() { bool flag = batchTotals.TotalFileTransferRequests == batchTotals.TotalCompletedFileTransfers; if (flag) Logger.LogInformation("Successfully waited for all {TransferJobId} file transfers to complete.", new object[1] { jobRequest?.JobId }); return flag; } private bool CheckDataInactivityTimeExceeded() { lock (syncRoot) { DateTime dateTime = transferActivityTimestamp; bool flag = (DateTime.Now - dateTime).TotalSeconds > maxInactivitySeconds; if (flag) Logger.LogInformation("Exceeded the max inactivity time of {MaxInactivitySeconds} seconds since the previous {LastTransferActivityTimestamp} timestamp update for the {TransferJobId} transfer job.", new object[3] { maxInactivitySeconds, dateTime, jobRequest?.JobId }); return flag; } } private bool CheckAbortOnRaisedPermissionIssues() { if (TransferJob == null) return false; bool flag = RaisedPermissionIssue && !parameters.PermissionErrorsRetry; if (flag) Logger.LogInformation("The transfer job {TransferJobId} will abort because a file permission issue was raised. {Error}", new object[2] { jobRequest?.JobId, RaisedPermissionIssueMessage }); return flag; } private bool CheckValidTransferJobStatus() { if (TransferJob == null) return false; TransferJobStatus status = TransferJob.Status; bool flag = status == TransferJobStatus.RetryPending || status == TransferJobStatus.Retrying || status == TransferJobStatus.Running || status == TransferJobStatus.Canceled; if (!flag) Logger.LogWarning("The transfer job {TransferJobId} status {TransferJobStatus} is neither running or retrying and is considered invalid.", new object[2] { jobRequest?.JobId, status }); return flag; } private IRelativityTransferHost CreateTransferHost() { if (relativityTransferHost == null) { GlobalSettings.Instance.StatisticsLogEnabled = false; RelativityConnectionInfo connectionInfo = tapiObjectService.CreateRelativityConnectionInfo(parameters); relativityTransferHost = tapiObjectService.CreateRelativityTransferHost(connectionInfo, Logger); } return relativityTransferHost; } private void CreateTransferClient(ClientConfiguration configuration) { Logger.LogInformation("TAPI client configuration {Configuration}", new object[1] { configuration }); IRelativityTransferHost val = CreateTransferHost(); val.Clear(); transferClient = val.CreateClient(configuration); UpdateAllTransferListenersClientName(); } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Never fail due to retrieving process info.")] private void CreateTransferJob(bool webModeSwitch, bool isRetry) { CheckDispose(); if (TransferJob == null) { Guid guid = Guid.NewGuid(); Logger.LogInformation("Create job {jobId} started...", new object[1] { guid }); CreateTransferClient(); lock (syncRoot) { transferActivityTimestamp = DateTime.Now; } if (!isRetry) ClearAllTotals(); currentJobNumber++; currentJobId = guid; RaisedPermissionIssue = false; RaisedPermissionIssueMessage = null; jobRequest = CreateTransferRequestForJob(transferContext); jobRequest.Application = parameters.Application; if (string.IsNullOrEmpty(jobRequest.Application)) try { string fileName = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; jobRequest.Application = Path.GetFileName(fileName); } catch (Exception) { jobRequest.Application = System.Diagnostics.Process.GetCurrentProcess().ProcessName; } jobRequest.ClientRequestId = parameters.ClientRequestId; jobRequest.JobId = currentJobId; jobRequest.Tag = currentJobNumber; jobRequest.Name = $"""{ClientId}""{currentJobNumber:""}"; jobRequest.RetryStrategy = RetryStrategies.CreateFixedTimeStrategy((double)parameters.WaitTimeBetweenRetryAttempts); SetupRemotePathResolvers(jobRequest); jobRequest.SubmitApmMetrics = parameters.SubmitApmMetrics; try { Task<ITransferJob> task = transferClient.CreateJobAsync(jobRequest, cancellationToken); TransferJob = task.GetAwaiter().GetResult(); Logger.LogInformation("Create job ended. {JobId}", new object[1] { jobRequest.JobId }); } catch (OperationCanceledException) { LogCancelRequest(); DestroyTransferJob(); throw; } catch (Exception ex3) { Logger.LogError(ex3, "Failed to create the transfer job.", Array.Empty<object>()); if (webModeSwitch) throw; SwitchTapiClientMode(ex3, TapiClient.Web); } } } private void CreateTransferClient<T>() where T : ClientConfiguration, new { T val = new T(); val.TransferLogDirectory = parameters.TransferLogDirectory; val.BadPathErrorsRetry = parameters.BadPathErrorsRetry; val.CookieContainer = parameters.WebCookieContainer; val.FileNotFoundErrorsDisabled = parameters.FileNotFoundErrorsDisabled; val.FileNotFoundErrorsRetry = parameters.FileNotFoundErrorsRetry; val.MaxJobParallelism = parameters.MaxJobParallelism; val.MaxJobRetryAttempts = parameters.MaxJobRetryAttempts; val.MaxHttpRetryAttempts = parameters.HttpErrorNumberOfRetries; val.PreserveDates = parameters.PreserveFileTimestamps; val.PermissionErrorsRetry = parameters.PermissionErrorsRetry; val.SavingMemoryMode = true; val.UseLegacyWebApi = useLegacyWebApi; CreateTransferClient(val); } private void CreateJobRetryListener() { transferListeners.Add(new TapiJobRetryListener(Logger, parameters.MaxJobRetryAttempts, transferContext)); } private void CreatePathIssueListener() { transferListeners.Add(new TapiPathIssueListener(Logger, currentDirection, transferContext)); transferContext.TransferPathIssue += OnTransferPathIssue; } private void CreatePathProgressListener() { TapiPathProgressListener tapiPathProgressListener = new TapiPathProgressListener(Logger, transferContext); tapiPathProgressListener.ProgressEvent += delegate(object sender, TapiProgressEventArgs args) { if (args.Completed) { batchTotals.IncrementTotalCompletedFileTransfers(); jobTotals.IncrementTotalCompletedFileTransfers(); } if (args.Successful) { batchTotals.IncrementTotalSuccessfulFileTransfers(); jobTotals.IncrementTotalSuccessfulFileTransfers(); } UpdateTransferActivityTimestamp(); this.TapiProgress?.Invoke(sender, args); }; tapiPathProgressListener.LargeFileProgressEvent += delegate(object sender, TapiLargeFileProgressEventArgs args) { UpdateTransferActivityTimestamp(); this.TapiLargeFileProgress?.Invoke(sender, args); }; transferListeners.Add(tapiPathProgressListener); } private void CreateRequestListener() { transferListeners.Add(new TapiRequestListener(Logger, transferContext)); } private void CreateStatisticsListener() { TapiStatisticsListener tapiStatisticsListener = new TapiStatisticsListener(Logger, transferContext); tapiStatisticsListener.StatisticsEvent += delegate(object sender, TapiStatisticsEventArgs args) { this.TapiStatistics?.Invoke(sender, args); }; transferListeners.Add(tapiStatisticsListener); } private void DestroyTransferClient() { if (transferClient != null) { transferClient.Dispose(); transferClient = null; UpdateAllTransferListenersClientName(); } } private void DestroyTransferHost() { if (relativityTransferHost != null) { ((IDisposable)relativityTransferHost).Dispose(); relativityTransferHost = null; } } private void DestroyTransferJob() { currentJobId = null; if (TransferJob != null) { TransferJob.Dispose(); TransferJob = null; } } private void DestroyTransferLog() { if (TransferLog != null) { TransferLog.Dispose(); TransferLog = null; } } private void DestroyTransferListeners() { if (transferListeners != null) { foreach (TapiListenerBase transferListener in transferListeners) { transferListener.Dispose(); } transferListeners.Clear(); transferContext.TransferPathIssue -= OnTransferPathIssue; } } private void Dispose(bool disposing) { if (!disposed) { if (disposing) { DestroyTransferJob(); DestroyTransferLog(); DestroyTransferClient(); DestroyTransferHost(); DestroyTransferListeners(); } disposed = true; } } private void SwitchTapiClientMode(Exception exception, TapiClient tapiClient) { Logger.LogWarning("Switching Tapi Client mode to {tapiClient}. Retrying in the original transfer mode value: {RetryInTheOriginalTransferMode}. With exception: {exception}", new object[3] { tapiClient, AppSettings.Instance.RetryInTheOriginalTransferMode, exception }); if (!AppSettings.Instance.RetryInTheOriginalTransferMode) tapiClient = TapiClient.Web; if (RaisedPermissionIssue) { Logger.LogError("Destroy transfer job {currentJobId} because of RaisedPermissionIssue=True", new object[1] { currentJobId }); DestroyTransferJob(); throw new TransferException(string.Format(CultureInfo.CurrentCulture, Strings.WebModeFallbackPermissionsFatalExceptionMessage, RaisedPermissionIssueMessage), exception, true); } if (transferClient?.Id == (Guid?)new Guid("18F9E382-288C-4B6D-8AE8-FBA4D88CD841")) { Logger.LogError("Destroy transfer job {currentJobId} because it was already WebMode", new object[1] { currentJobId }); DestroyTransferJob(); throw new TransferException(Strings.WebModeFallbackAlreadyWebModeFatalExceptionMessage, exception, true); } string message = string.Format(CultureInfo.CurrentCulture, Strings.WebModeFallbackNoErrorWarningMessage, ClientDisplayName); if (tapiClient != TapiClient.Web) message = string.Format(CultureInfo.CurrentCulture, Strings.TransferFallbackWarningMessage, tapiClient, ClientDisplayName, (exception == null) ? "(null)" : exception.Message); else if (exception != null) { message = string.Format(CultureInfo.CurrentCulture, Strings.WebModeFallbackWarningMessage, ClientDisplayName, exception.Message); } PublishWarningMessage(message, 0); IList<TransferPath> retryableTransferPaths = GetRetryableTransferPaths(); DestroyTransferJob(); DestroyTransferClient(); CreateFallbackTapiClient(tapiClient); CreateTransferJob(tapiClient == TapiClient.Web, true); if (retryableTransferPaths.Count == 0) Logger.LogInformation("No retryable paths exist.", Array.Empty<object>()); else { Logger.LogInformation("Adding {count} retryable paths.", new object[1] { retryableTransferPaths.Count }); foreach (TransferPath item in retryableTransferPaths) { item.RevertPaths(); TransferJob.AddPath(item, cancellationToken); } } } private void CreateFallbackTapiClient(TapiClient tapiClient) { switch (tapiClient) { case TapiClient.Aspera: this.CreateTransferClient<AsperaClientConfiguration>(); PublishClientChanged(ClientChangeReason.HttpFallback); break; case TapiClient.Direct: this.CreateTransferClient<FileShareClientConfiguration>(); PublishClientChanged(ClientChangeReason.HttpFallback); break; default: this.CreateTransferClient<HttpClientConfiguration>(); PublishClientChanged(ClientChangeReason.HttpFallback); break; } OptimizeClient(); Logger.LogInformation("Successfully switched to {tapiClient} mode.", new object[1] { tapiClient }); } private bool GetIsTransferPathInJobQueue(TransferPath path) { if (TransferJob == null) return false; return TransferJob.JobService.GetJobTransferPath(path) != null; } private IList<TransferPath> GetRetryableTransferPaths() { List<TransferPath> paths = new List<TransferPath>(); if (TransferJob != null) { IReadOnlyCollection<TransferPath> retryableRequestTransferPaths = TransferJob.JobService.GetRetryableRequestTransferPaths(); paths.AddRange(retryableRequestTransferPaths); Logger.LogInformation("Number of retryable paths from tapi: {paths}", new object[1] { retryableRequestTransferPaths.Count }); TransferPath[] array = (from jobPath in TransferJob.JobService.GetJobTransferPaths().Where(delegate(JobTransferPath x) { if (x.Status == TransferPathStatus.Fatal) return !paths.Contains(x.Path); return false; }) select jobPath.Path).ToArray(); Logger.LogInformation("Number of failed Fatal paths from tapi: {paths}", new object[1] { array.Length }); paths.AddRange(array); } else Logger.LogWarning("Trying to get retryable paths but TransferJob is null", Array.Empty<object>()); Logger.LogInformation("Total number of retryable paths: {TotalRetryablePaths:n0}", new object[1] { paths.Count }); Logger.LogInformation("Total number of retryable bytes: {TotalRetryableBytes:n0}", new object[1] { paths.Sum((TransferPath x) => x.Bytes) }); return paths; } private void IncrementTotalFileTransferRequests() { batchTotals.IncrementTotalFileTransferRequests(); jobTotals.IncrementTotalFileTransferRequests(); } private void LogCancelRequest() { Logger.LogInformation("The file transfer has been cancelled. ClientId={ClientId}, JobId={JobId} ", new object[2] { parameters.ClientRequestId, jobRequest?.JobId }); } private void LogTransferTotals(string prefix, bool completed) { StringBuilder stringBuilder = new StringBuilder("WaitForTransfers-" + prefix + ": "); if (!completed) { stringBuilder.Append("Awaiting {TotalFileTransferRequests:n0} transfer files from {TransferJobId} using {TransferMode} mode."); Logger.LogInformation(stringBuilder.ToString(), new object[3] { jobTotals.TotalFileTransferRequests, jobRequest?.JobId, ClientDisplayName }); } else { stringBuilder.Append("Completed {TotalSuccessfulFileTransfers:n0} of {TotalFileTransferRequests:n0} transfer files from {TransferJobId} using {TransferMode} mode."); Logger.LogInformation(stringBuilder.ToString(), new object[4] { jobTotals.TotalSuccessfulFileTransfers, jobTotals.TotalFileTransferRequests, jobRequest?.JobId, ClientDisplayName }); if (jobTotals.TotalFileTransferRequests == 0) Logger.LogWarning("Although the {TransferJobId} transfer job completed, the total number of file requests is zero and may suggest a logic issue or unexpected result.", new object[1] { jobRequest?.JobId }); } } private void OnTransferPathIssue(object sender, TransferPathIssueEventArgs e) { if (e.Issue.Attributes.HasFlag(IssueAttributes.ReadWritePermissions)) { RaisedPermissionIssue = true; RaisedPermissionIssueMessage = e.Issue.Message; } } private void OptimizeClient() { if (transferClient != null) { string a = transferClient.Id.ToString().ToUpperInvariant(); if (!(a == "0315D6C7-FF07-41E2-9C25-16573FC6B9DE")) { if (!(a == "18F9E382-288C-4B6D-8AE8-FBA4D88CD841")) { if (a == "812A70A5-7311-46CC-BE53-07BB8C5F9A7D") transferClient.Configuration.MaxJobParallelism = 1; } else transferClient.Configuration.MaxJobParallelism = 1; } } } private void PublishClientChanged(ClientChangeReason reason) { CheckDispose(); string message; switch (reason) { case ClientChangeReason.BestFit: message = string.Format(CultureInfo.CurrentCulture, Strings.TransferClientChangedBestFitMessage, ClientDisplayName); break; case ClientChangeReason.ForceConfig: message = string.Format(CultureInfo.CurrentCulture, Strings.TransferClientChangedForceConfigMessage, ClientDisplayName); break; case ClientChangeReason.HttpFallback: message = string.Format(CultureInfo.CurrentCulture, Strings.TransferClientChangedHttpFallbackMessage, ClientDisplayName); break; default: message = string.Format(CultureInfo.CurrentCulture, Strings.TransferClientChangedDefaultMessage, ClientDisplayName); break; } PublishStatusMessage(message, 0); TapiClientEventArgs e = new TapiClientEventArgs(InstanceId, ClientDisplayName, Client); this.TapiClientChanged?.Invoke(this, e); UpdateAllTransferListenersClientName(); } private void PublishFatalError(string message, int lineNumber) { CheckDispose(); this.TapiFatalError?.Invoke(this, new TapiMessageEventArgs(message, lineNumber)); } private void PublishStatusMessage(string message, int lineNumber) { CheckDispose(); this.TapiStatusMessage?.Invoke(this, new TapiMessageEventArgs(message, lineNumber)); } private void PublishWarningMessage(string message, int lineNumber) { CheckDispose(); this.TapiWarningMessage?.Invoke(this, new TapiMessageEventArgs(message, lineNumber)); } private void SetupTransferListeners() { CreatePathProgressListener(); CreatePathIssueListener(); CreateRequestListener(); CreateJobRetryListener(); CreateStatisticsListener(); foreach (TapiListenerBase transferListener in transferListeners) { transferListener.ErrorMessage += delegate(object sender, TapiMessageEventArgs args) { this.TapiErrorMessage?.Invoke(sender, args); }; transferListener.FatalError += delegate(object sender, TapiMessageEventArgs args) { this.TapiFatalError?.Invoke(sender, args); }; transferListener.StatusMessage += delegate(object sender, TapiMessageEventArgs args) { this.TapiStatusMessage?.Invoke(sender, args); }; transferListener.WarningMessage += delegate(object sender, TapiMessageEventArgs args) { this.TapiWarningMessage?.Invoke(sender, args); }; } } private void UpdateAllTransferListenersClientName() { if (!disposed) { string clientDisplayName = ClientDisplayName; foreach (TapiListenerBase transferListener in transferListeners) { transferListener.ClientDisplayName = clientDisplayName; } } } private void UpdateTransferActivityTimestamp() { lock (syncRoot) { transferActivityTimestamp = DateTime.Now; } } private void ValidateTransferPath(TransferPath path) { if (string.IsNullOrWhiteSpace(path.SourcePath) && (!path.SourcePathId.HasValue || path.SourcePathId.Value < 1)) throw new ArgumentException((currentDirection == TransferDirection.Download || path.Direction == TransferDirection.Download) ? Strings.TransferPathArgumentDownloadSourcePathExceptionMessage : Strings.TransferPathArgumentUploadSourcePathExceptionMessage, "path"); if (string.IsNullOrWhiteSpace(path.TargetPath) && string.IsNullOrWhiteSpace(TargetPath)) throw new ArgumentException((currentDirection == TransferDirection.Download || path.Direction == TransferDirection.Download) ? Strings.TransferPathArgumentDownloadTargetPathExceptionMessage : Strings.TransferPathArgumentUploadTargetPathExceptionMessage, "path"); } private TapiTotals WaitForCompletedTransfers() { Logger.LogInformation("Preparing to wait for the {TransferJobId} batched file transfers to complete...", new object[1] { jobRequest?.JobId }); try { int originalClientModeRetryCounter = 0; RetryTResultSyntax.WaitAndRetryForever<bool>(Policy.HandleResult<bool>(false), (Func<int, TimeSpan>)((int i) => TimeSpan.FromMilliseconds(250)), (Action<DelegateResult<bool>, TimeSpan>)delegate { originalClientModeRetryCounter++; }).Execute((Func<bool>)delegate { try { if (!CheckValidTransferJobStatus() && originalClientModeRetryCounter < 2) SwitchTapiClientMode(null, Client); cancellationToken.ThrowIfCancellationRequested(); return CheckCompletedTransfers() || CheckDataInactivityTimeExceeded() || CheckAbortOnRaisedPermissionIssues(); } catch (OperationCanceledException) { LogCancelRequest(); DestroyTransferJob(); throw; } catch (Exception ex2) { if (ExceptionHelper.IsFatalException(ex2)) throw; Logger.LogError(ex2, "An exception was thrown waiting for the {TransferJobId} file transfers to complete.", new object[1] { jobRequest?.JobId }); SwitchTapiClientMode(ex2, TapiClient.Web); return true; } }); if (!CheckCompletedTransfers()) { Logger.LogWarning("WaitForCompletedTransfers has exited, not all {TransferJobId} file transfers have completed, and now going to wait for the transfer job to complete.", new object[1] { jobRequest?.JobId }); LogTransferTotals("Inactivity", false); WaitForCompletedTransferJob(); } return batchTotals.DeepCopy(); } finally { batchTotals.Clear(); } } private TapiTotals WaitForCompletedTransferJob() { if (TransferJob != null) { Logger.LogInformation("Preparing to wait for transfer job {TransferJobId} to complete...", new object[1] { jobRequest?.JobId }); try { Exception handledException = null; RetrySyntax.Retry(Policy.Handle<TransferException>(), 2, (Action<Exception, int>)delegate(Exception exception, int count) { handledException = exception; Logger.LogWarning(exception, "An unexpected error has occurred attempting to wait for transfer job {TransferJobId} to complete.", new object[1] { jobRequest?.JobId }); SwitchTapiClientMode(exception, (count >= 2) ? TapiClient.Web : Client); }).Execute((Action)delegate { ITransferResult result = TransferJob.CompleteAsync(cancellationToken).GetAwaiter().GetResult(); Logger.LogInformation("Transfer job {TransferJobId} completed. {Name} transfer status: {Status}, elapsed time: {Elapsed}, data rate: {TransferRate:0.00} Mbps", new object[5] { jobRequest?.JobId, ClientDisplayName, result.Status, result.Elapsed, result.TransferRateMbps }); IssueAttributes missingFilesFlag = IssueAttributes.FileNotFound | IssueAttributes.Io | IssueAttributes.Warning; if (result.Status == TransferStatus.Fatal || (result.Status == TransferStatus.Failed && (result.Issues.Count == 0 || result.Issues.Any((ITransferIssue x) => !x.Attributes.HasFlag(missingFilesFlag))))) { LogTransferTotals("NotSuccessful", true); string transferErrorMessage = GetTransferErrorMessage(result); PublishStatusMessage(transferErrorMessage, 0); if (handledException == null) throw new TransferException(transferErrorMessage); PublishFatalError(transferErrorMessage, 0); } }); return jobTotals.DeepCopy(); } catch (OperationCanceledException) { LogCancelRequest(); throw; } finally { DestroyTransferJob(); } } return jobTotals.DeepCopy(); } } }