TransferClientBase
using Relativity.Transfer.Resources;
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
namespace Relativity.Transfer
{
public abstract class TransferClientBase : ITransferClient, IDisposable
{
private static readonly object SyncRoot = new object();
private bool disposed;
private Workspace workspace;
public WellKnownTransferClient Client { get; }
public RelativityConnectionInfo ConnectionInfo { get; }
public ClientConfiguration Configuration { get; }
public string DisplayName { get; }
public Guid Id { get; }
public string Name { get; }
internal IRelativityServiceFactory ServiceFactory { get; }
protected IFileSystemService FileSystemService { get; }
protected ITransferLog Log { get; }
protected IPathValidationProvider PathValidationProvider => ServiceFactory.PathValidationProvider;
protected Workspace Workspace {
get {
lock (SyncRoot) {
return workspace;
}
}
private set {
lock (SyncRoot) {
workspace = value;
}
}
}
protected IWorkspaceService WorkspaceService { get; }
protected TransferClientBase(RelativityConnectionInfo connectionInfo, ClientConfiguration configuration, IWorkspaceService workspaceService, ITransferLog log, Guid id, WellKnownTransferClient client, string name, string displayName)
: this(connectionInfo, configuration, workspaceService, log, ServiceObjectLocator.GetService<IFileSystemService>(), id, client, name, displayName, new DefaultPathValidationFactory())
{
}
protected TransferClientBase(RelativityConnectionInfo connectionInfo, ClientConfiguration configuration, IWorkspaceService workspaceService, ITransferLog log, Guid id, WellKnownTransferClient client, string name, string displayName, IPathValidationFactory pathValidationFactory)
: this(connectionInfo, configuration, workspaceService, log, ServiceObjectLocator.GetService<IFileSystemService>(), id, client, name, displayName, pathValidationFactory)
{
}
protected TransferClientBase(RelativityConnectionInfo connectionInfo, ClientConfiguration configuration, IWorkspaceService workspaceService, ITransferLog log, IFileSystemService fileSystemService, Guid id, WellKnownTransferClient client, string name, string displayName, IPathValidationFactory pathValidationFactory)
{
if (connectionInfo == null)
throw new ArgumentNullException("connectionInfo");
if (connectionInfo.Credential == null)
throw new ArgumentException(CoreStrings.RelativityConnectionCredentialsArgumentExceptionMessage, "connectionInfo");
if (connectionInfo.Host == (Uri)null)
throw new ArgumentException(CoreStrings.RelativityConnectionHostArgumentExceptionMessage, "connectionInfo");
if (connectionInfo.WorkspaceId < 1 && connectionInfo.WorkspaceId != -1)
throw new ArgumentOutOfRangeException("connectionInfo", CoreStrings.RelativityConnectionWorkspaceArgumentExceptionMessage);
if (configuration == null)
throw new ArgumentNullException("configuration");
if (workspaceService == null)
throw new ArgumentNullException("workspaceService");
if (log == null)
throw new ArgumentNullException("log");
if (id == Guid.Empty)
throw new ArgumentNullException("id");
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException("name");
if (string.IsNullOrEmpty(displayName))
throw new ArgumentNullException("displayName");
if (fileSystemService == null)
throw new ArgumentNullException("fileSystemService");
if (pathValidationFactory == null)
throw new ArgumentNullException("pathValidationFactory");
Client = client;
Configuration = configuration;
ConnectionInfo = connectionInfo;
Log = log;
Id = id;
Name = name;
DisplayName = displayName;
Workspace = null;
WorkspaceService = workspaceService;
FileSystemService = fileSystemService;
ServiceFactory = new ServiceFactory(ConnectionInfo, Log, Configuration.MaxHttpRetryAttempts, Configuration.HttpTimeoutSeconds, pathValidationFactory.CreatePathValidationProvider());
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public Task<ITransferJob> CreateJobAsync(ITransferRequest request)
{
if (request == null)
throw new ArgumentNullException("request");
return CreateJobAsync(request, CancellationToken.None);
}
[Obsolete("This enumeration support will be removed in next release. In order to perform file enumeration use EnumerationBuilder and IEnumerationOrchestrator")]
public IPathEnumerator CreatePathEnumerator(bool local)
{
return local ? OnCreateLocalPathEnumerator() : OnCreateRemotePathEnumerator();
}
public IBatchSerializer CreateBatchSerializer(string batchDirectory, TransferDirection direction, PathEnumeratorContext context)
{
return new BatchSerializer(batchDirectory, direction, context, PathValidationProvider, Log);
}
public virtual async Task<ITransferJob> CreateJobAsync(ITransferRequest request, CancellationToken token)
{
if (request == null)
throw new ArgumentNullException("request");
Log.LogTransferInformation(request, "Received request to create transfer job: {request}", request);
VerifyIfTargetPathExists(request);
await InitializeClientAsync(token).ConfigureAwait(false);
Log.LogTransferInformation(request, "Preparing to create the {Name} transfer job...", Name);
ITransferJob job = await OnCreateJobAsync(request, token).ConfigureAwait(false);
Log.LogTransferInformation(request, "Successfully created the {Name} transfer job.", Name);
return job;
}
public Task<Workspace> GetWorkspaceAsync()
{
return GetWorkspaceAsync(CancellationToken.None);
}
public Task<Workspace> GetWorkspaceAsync(CancellationToken token)
{
return GetWorkspaceAsync(ConnectionInfo.WorkspaceId, CancellationToken.None);
}
public async Task<Workspace> GetWorkspaceAsync(int workspaceArtifactId, CancellationToken token)
{
string key = CoreMemoryCacheKeys.CreateWorkspaceKey(ConnectionInfo.Host, workspaceArtifactId);
using (MemoryCacheRepository cacheRepository = new MemoryCacheRepository(TransferCache.Default)) {
Workspace currentWorkspace = cacheRepository.SelectByKey<Workspace>(key);
if (currentWorkspace == null) {
Log.LogDebug("Retrieving the '{WorkspaceId}' workspace information.", workspaceArtifactId);
currentWorkspace = await WorkspaceService.GetWorkspaceAsync(workspaceArtifactId, token).ConfigureAwait(false);
if (currentWorkspace != null)
Log.LogDebug("Successfully retrieved the '{WorkspaceId}' workspace information.", workspaceArtifactId);
else
Log.LogWarning("The '{WorkspaceId}' workspace does not exist.", workspaceArtifactId);
}
if (currentWorkspace != null)
cacheRepository.Upsert(key, currentWorkspace);
return currentWorkspace;
}
}
public virtual Task<ITransferResult> TransferAsync(ITransferRequest request)
{
if (request == null)
throw new ArgumentNullException("request");
return TransferAsync(request, CancellationToken.None);
}
public virtual async Task<ITransferResult> TransferAsync(ITransferRequest request, CancellationToken token)
{
if (request == null)
throw new ArgumentNullException("request");
if (request.Paths.Count == 0)
throw new ArgumentException(CoreStrings.TransferRequestNoTransferPathsArgumentExceptionMessage, "request");
using (ITransferJob job = await CreateJobAsync(request, token).ConfigureAwait(false)) {
foreach (TransferPath path in request.Paths) {
job.AddPath(path);
}
return await job.CompleteAsync(TransferConstants.DefaultWaitTimeout, token).ConfigureAwait(false);
}
}
public Task<ISupportCheckResult> SupportCheckAsync()
{
return SupportCheckAsync(CancellationToken.None);
}
public async Task<ISupportCheckResult> SupportCheckAsync(CancellationToken token)
{
Log.LogInformation("The support check for the {Name} client is requested...", Name);
Log.LogInformation("Preparing to auto-configure the {Name} client...", Name);
string key = CoreMemoryCacheKeys.CreateSupportCheckKey(ConnectionInfo, Id, Configuration.SupportCheckPath);
using (MemoryCacheRepository cacheRepository = new MemoryCacheRepository(TransferCache.Default)) {
try {
await OnAutoConfigAsync(token).ConfigureAwait(false);
} catch (OperationCanceledException) {
throw;
} catch (Exception e2) {
Log.LogWarning(e2, "Failed to auto-configure the {Name} support check.", Name);
SupportCheckResult failedResult = new SupportCheckResult {
ErrorMessage = e2.Message,
IsSupported = false
};
cacheRepository.Upsert(key, failedResult);
return failedResult;
}
Log.LogInformation("Successfully auto-configured the {Name} client.", Name);
Log.LogInformation("Checking the support check cache repository for the {Name} client...", Name);
ISupportCheckResult result2 = cacheRepository.SelectByKey<ISupportCheckResult>(key);
if (result2 == null)
try {
Log.LogInformation("Preparing to execute the {Name} support check...", Name);
result2 = await OnSupportCheckAsync(token).ConfigureAwait(false);
cacheRepository.Upsert(key, result2);
Log.LogInformation("Successfully executed the {Name} support check. Result={IsSupported}.", Name, result2.IsSupported);
return result2;
} catch (Exception e) {
Log.LogWarning(e, "Failed to execute the {Name} support check.", Name);
throw;
} finally {
Log.LogInformation("The support check for the {Name} client is completed.", Name);
}
Log.LogInformation("The support check cache repository contains an entry for the {Name} client. Result={IsSupported}.", Name, result2.IsSupported);
return result2;
}
}
public Task<IConnectionCheckResult> ConnectionCheckAsync(DiagnosticsContext context)
{
if (context == null)
throw new ArgumentNullException("context");
return ConnectionCheckAsync(context, CancellationToken.None);
}
public async Task<IConnectionCheckResult> ConnectionCheckAsync(DiagnosticsContext context, CancellationToken token)
{
if (context != null) {
Log.LogInformation("Executing connection test for the {Name} client.", Name);
try {
string key = CoreMemoryCacheKeys.CreateWorkspaceKey(ConnectionInfo.Host, ConnectionInfo.WorkspaceId);
using (MemoryCacheRepository cacheRepository = new MemoryCacheRepository(TransferCache.Default))
Workspace = cacheRepository.SelectByKey<Workspace>(key);
if (ConnectionInfo.WorkspaceId == -1 && Configuration.TargetFileShare != null) {
context.PublishStatusMessage(message: string.Format(CoreStrings.ConnectionCheckUsingFileShare, Configuration.TargetFileShare.Name), clientId: Id);
if (Workspace == null)
await InitializeWorkspaceAsync(token).ConfigureAwait(false);
} else {
context.PublishStatusMessage(message: string.Format(CultureInfo.CurrentCulture, CoreStrings.ConnectionCheckGetWorkspaceMessage, ConnectionInfo.WorkspaceId), clientId: Id);
if (Workspace == null)
await InitializeWorkspaceAsync(token).ConfigureAwait(false);
context.PublishStatusMessage(Id, CoreStrings.ConnectionCheckGetWorkspaceSuccessMessage);
}
context.PublishStatusMessage(Id, string.Empty);
return await OnExecConnectionCheckAsync(context, token).ConfigureAwait(false);
} finally {
Log.LogInformation("The connection test for the {Name} client is completed.", Name);
}
}
throw new ArgumentNullException("context");
}
public override string ToString()
{
return $"""{Name}""{Id}";
}
protected IRelativityServiceFactory CreateRelativityServiceFactory()
{
return new ServiceFactory(ConnectionInfo, Log);
}
protected virtual ITransferJobService CreateTransferJobService(ITransferRequest request, CancellationToken token)
{
return CreateTransferJobService(request, Configuration, token);
}
protected virtual ITransferJobService CreateTransferJobService(ITransferRequest request, ClientConfiguration configuration, CancellationToken token)
{
return new TransferJobService(request, configuration, Log, token);
}
protected virtual Task OnAutoConfigAsync(CancellationToken token)
{
return Task.FromResult(true);
}
[Obsolete("This enumeration support will be removed in next release. In order to perform file enumeration use EnumerationBuilder and IEnumerationOrchestrator")]
protected virtual IPathEnumerator OnCreateLocalPathEnumerator()
{
return new LocalPathEnumerator(FileSystemService, PathValidationProvider, Log);
}
[Obsolete("This enumeration support will be removed in next release. In order to perform file enumeration use EnumerationBuilder and IEnumerationOrchestrator")]
protected virtual IPathEnumerator OnCreateRemotePathEnumerator()
{
throw new NotSupportedException(CoreStrings.RemotePathEnumeratorNotSupported);
}
protected abstract Task<ISupportCheckResult> OnSupportCheckAsync(CancellationToken token);
protected abstract Task<ITransferJob> OnCreateJobAsync(ITransferRequest request, CancellationToken token);
protected abstract Task<IConnectionCheckResult> OnExecConnectionCheckAsync(DiagnosticsContext context, CancellationToken token);
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (!disposing)
goto IL_0015;
goto IL_0015;
IL_0015:
disposed = true;
}
private async Task InitializeClientAsync(CancellationToken token)
{
Log.LogInformation("Preparing to initialize the {Name} client...", Name);
await InitializeWorkspaceAsync(token).ConfigureAwait(false);
Log.LogInformation("Successfully initialized the {Name} client.", Name);
Log.LogInformation("Preparing to auto-configure the {Name} client...", Name);
await OnAutoConfigAsync(token).ConfigureAwait(false);
Log.LogInformation("Successfully auto-configured the {Name} client.", Name);
}
private async Task InitializeWorkspaceAsync(CancellationToken token)
{
Workspace = await GetWorkspaceAsync(token).ConfigureAwait(false);
if (Workspace == null) {
string message = string.Format(CultureInfo.CurrentCulture, CoreStrings.WorkspaceNotFoundExceptionMessage, ConnectionInfo.WorkspaceId);
throw new TransferException(message, true);
}
}
private void VerifyIfTargetPathExists(ITransferRequest request)
{
if (string.IsNullOrEmpty(request.TargetPath)) {
foreach (TransferPath path in request.Paths) {
if (string.IsNullOrEmpty(path.TargetPath))
Log.LogTransferWarning(request, "Transfer Path (Source: {SourcePath}) has no target path set, neither does transfer job itself. Transfer may fail.", path.SourcePath, LogRedaction.OnPositions(default(int)));
}
}
}
}
}