using Amazon.S3.Model; using Amazon.S3.Transfer; using Amazon.S3.Wrapper.Enums; using Amazon.S3.Wrapper.Exceptions; using Amazon.S3.Wrapper.Extensions; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Amazon.S3.Wrapper { public class S3Client : IS3Client { private readonly S3StorageClass _storageClass; private readonly ContentType _contentType; private readonly string _bucketName; private readonly IAmazonS3 _client; /// /// Creates a new instance of amazon s3 client /// /// Client for accessing S3 /// Bucket name /// Default value xml /// Default value S3StorageClass.Standard public S3Client(IAmazonS3 amazonS3, string bucketName, ContentType? contentType, S3StorageClass storageClass) { if (string.IsNullOrWhiteSpace(bucketName)) { throw new ArgumentNullException(nameof(bucketName)); } _client = amazonS3; _bucketName = bucketName; _contentType = contentType ?? ContentType.Json; _storageClass = storageClass ?? S3StorageClass.Standard; } public S3Client(string region, string bucketName, ContentType? contentType, S3StorageClass storageClass) { if (string.IsNullOrWhiteSpace(region)) { throw new ArgumentNullException(nameof(region)); } if (string.IsNullOrWhiteSpace(bucketName)) { throw new ArgumentNullException(nameof(bucketName)); } var s3Config = new AmazonS3Config { RegionEndpoint = RegionEndpoint.GetBySystemName(region), MaxErrorRetry = 1, }; _client = new AmazonS3Client(s3Config); _bucketName = bucketName; _contentType = contentType ?? ContentType.Json; _storageClass = storageClass ?? S3StorageClass.Standard; } /// /// Upload a object as multipart upload to s3 bucket defined in initialization /// /// This key is used to identify the object in S3 /// content to be uploaded /// PutObjectResponse object public async Task UploadObjectAsync(string key, Stream content, CancellationToken cancellationToken = default) { var result = await UploadObjectAsync(key, content, _contentType, _storageClass, cancellationToken).ConfigureAwait(false); return result; } /// /// Upload a object to s3 bucket using Multipart upload /// /// This key is used to identify the object in S3 /// content to be uploaded /// Overrides contentype provided in registry /// Overrides storageClass provided in registry /// public async Task UploadObjectAsync(string key, Stream content, ContentType? contentType = null, S3StorageClass storageClass = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException(nameof(key)); } var requestStorageClass = storageClass ?? _storageClass; var requestContentType = contentType ?? _contentType; var transferUtility = new TransferUtility(_client); try { var request = new TransferUtilityUploadRequest { BucketName = _bucketName, Key = key, StorageClass = requestStorageClass, ContentType = requestContentType.Value(), InputStream = content }; await transferUtility.UploadAsync(request, cancellationToken); return true; } catch (Exception e) { var errorMessage = $"Error encountered {e.Message}.{Environment.NewLine}" + $"It was not possible to upload the object.{Environment.NewLine}" + $"BucketName: {_bucketName}{Environment.NewLine}" + $"Key: {key}{Environment.NewLine}" + $"ContentBody: {content}{Environment.NewLine}"; //TODO: Try to abort the upload using upload id await transferUtility.AbortMultipartUploadsAsync(_bucketName, DateTime.Now); throw new AmazonS3ClientException(errorMessage, e); } } /// /// Upload a object to s3 bucket defined in initialization /// /// This key is used to identify the object in S3 /// content to be uploaded /// PutObjectResponse object public async Task PutObjectAsync(string key, string content, CancellationToken cancellationToken = default) { var result = await PutObjectAsync(key, content, _contentType, _storageClass, cancellationToken).ConfigureAwait(false); return result; } /// /// Upload a object to s3 bucket /// /// This key is used to identify the object in S3 /// content to be uploaded /// Overrides contentype provided in registry /// Overrides storageClass provided in registry /// public async Task PutObjectAsync(string key, string content, ContentType? contentType = null, S3StorageClass storageClass = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException(nameof(key)); } if (string.IsNullOrWhiteSpace(content)) { throw new ArgumentNullException(nameof(content)); } var requestStorageClass = storageClass ?? _storageClass; var requestContentType = contentType ?? _contentType; try { var putRequest = new PutObjectRequest { BucketName = _bucketName, Key = key, StorageClass = requestStorageClass, ContentType = requestContentType.Value(), ContentBody = content }; var result = await _client.PutObjectAsync(putRequest, cancellationToken).ConfigureAwait(false); return result; } catch (Exception e) { var errorMessage = $"Error encountered {e.Message}.{Environment.NewLine}" + $"It was not possible to upload the object.{Environment.NewLine}" + $"BucketName: {_bucketName}{Environment.NewLine}" + $"Key: {key}{Environment.NewLine}" + $"StorageClass: {requestStorageClass}{Environment.NewLine}" + $"ContentType: {requestContentType}{Environment.NewLine}" + $"ContentBody: {content}{Environment.NewLine}"; throw new AmazonS3ClientException(errorMessage, e); } } /// /// Get object from s3 bucket /// /// This key is used to identify the object in S3 /// public async Task GetObjectAsync(string key, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException(nameof(key)); } try { var request = new GetObjectRequest { BucketName = _bucketName, Key = key }; var result = await _client.GetObjectAsync(request, cancellationToken).ConfigureAwait(false); return result; } catch (Exception e) { var errorMessage = $"Error encountered {e.Message}.{Environment.NewLine}" + $"It was not possible to get the object.{Environment.NewLine}" + $"BucketName: {_bucketName}{Environment.NewLine}" + $"Key: {key}{Environment.NewLine}"; throw new AmazonS3ClientException(errorMessage, e); } } /// /// Delete a file object from bucket. /// /// This key is used to identify the object in S3 /// Propagates notification that operations should be canceled. /// GetObjectResponse public async Task DeleteObjectAsync(string key, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException(nameof(key)); } try { var deleteObjectRequest = new DeleteObjectRequest { BucketName = _bucketName, Key = key }; var deleteResult = await _client.DeleteObjectAsync(deleteObjectRequest, cancellationToken).ConfigureAwait(false); return deleteResult; } catch (Exception e) { var errorMessage = $"Error encountered {e.Message}.{Environment.NewLine}" + $"It was not possible to delete the object.{Environment.NewLine}" + $"BucketName: {_bucketName}{Environment.NewLine}" + $"Key: {key}{Environment.NewLine}"; throw new AmazonS3ClientException(errorMessage, e); } } /// /// Delete a list of objects from bucket. /// /// List of object keys to delete. /// Propagates notification that operations should be canceled. /// DeleteObjectsResponse public async Task DeleteObjectsAsync(IEnumerable keys, CancellationToken cancellationToken = default) { if (keys.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(keys)); } if (keys.Any(key => string.IsNullOrWhiteSpace(key))) { throw new Exception("Element keys cannot be null or empty."); } try { var deleteObjectsRequest = new DeleteObjectsRequest { BucketName = _bucketName, Objects = new List(keys.Select(key => new KeyVersion() { Key = key })) }; var deleteResult = await _client.DeleteObjectsAsync(deleteObjectsRequest, cancellationToken).ConfigureAwait(false); return deleteResult; } catch (Exception e) { var errorMessage = $"Error encountered {e.Message}.{Environment.NewLine}" + $"It was not possible to delete the objects.{Environment.NewLine}" + $"BucketName: {_bucketName}{Environment.NewLine}" + $"Key: {string.Join(",", keys)}{Environment.NewLine}"; throw new AmazonS3ClientException(errorMessage, e); } } /// /// Check if the file exists or not. /// /// List of object keys to delete. /// Propagates notification that operations should be canceled. /// DeleteObjectsResponse public async Task IsFileExistsAsync(string fileName, string versionId) { try { GetObjectMetadataRequest request = new GetObjectMetadataRequest() { BucketName = _bucketName, Key = fileName, VersionId = !string.IsNullOrEmpty(versionId) ? versionId : null }; var response = await _client.GetObjectMetadataAsync(request); return true; } catch (Exception ex) { if (ex.InnerException != null && ex.InnerException is AmazonS3Exception awsEx) { if (string.Equals(awsEx.ErrorCode, "NoSuchBucket")) return false; else if (string.Equals(awsEx.ErrorCode, "NotFound")) return false; } throw; } } } }