431 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			431 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using Azure;
 | |
| using Azure.Storage;
 | |
| using Azure.Storage.Blobs;
 | |
| using Azure.Storage.Blobs.Models;
 | |
| using Azure.Storage.Blobs.Specialized;
 | |
| using Azure.Storage.Sas;
 | |
| using Core.Blueprint.Storage.Adapters;
 | |
| using Core.Blueprint.Storage.Contracts;
 | |
| using Microsoft.Extensions.Configuration;
 | |
| using System.Threading.Tasks;
 | |
| 
 | |
| namespace Core.Blueprint.Storage.Provider
 | |
| {
 | |
|     public sealed class BlobStorageProvider : IBlobStorageProvider
 | |
|     {
 | |
|         private readonly BlobServiceClient _blobServiceClient;
 | |
|         private readonly BlobContainerClient _blobContainerClient;
 | |
|         private readonly string _containerName;
 | |
|         private readonly Trie _trie = new Trie();
 | |
|         private readonly IConfiguration _configuration;
 | |
| 
 | |
|         public BlobStorageProvider(BlobServiceClient blobServiceClient, IConfiguration configuration)
 | |
|         {
 | |
|             _blobServiceClient = blobServiceClient;
 | |
|             _configuration = configuration;
 | |
|             _containerName = configuration.GetSection("BlobStorage:ContainerName").Value ?? "";
 | |
| 
 | |
|             if (string.IsNullOrEmpty(_containerName))
 | |
|                 throw new ArgumentException("Blob container cannot be null or empty.");
 | |
| 
 | |
|             _blobContainerClient = blobServiceClient.GetBlobContainerClient(_containerName);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Creates the blob container if it does not exist.
 | |
|         /// </summary>
 | |
|         public async Task<Response<BlobContainerInfo>> CreateIfNotExistsAsync()
 | |
|         {
 | |
|             return await _blobContainerClient.CreateIfNotExistsAsync();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Deletes the blob container if it exists.
 | |
|         /// </summary>
 | |
|         public async Task DeleteIfExistsAsync()
 | |
|         {
 | |
|             await _blobContainerClient.DeleteIfExistsAsync();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Gets properties of the blob container.
 | |
|         /// </summary>
 | |
|         public async Task<Response<BlobContainerProperties>> GetPropertiesAsync()
 | |
|         {
 | |
|             return await _blobContainerClient.GetPropertiesAsync();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sets metadata for the blob container.
 | |
|         /// </summary>
 | |
|         public async Task SetMetadataAsync(IDictionary<string, string> metadata)
 | |
|         {
 | |
|             await _blobContainerClient.SetMetadataAsync(metadata);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Uploads a blob to the container.
 | |
|         /// </summary>
 | |
|         public async Task<Response<BlobContentInfo>> UploadBlobAsync(string blobName, Stream content)
 | |
|         {
 | |
|             var blobClient = _blobContainerClient.GetBlobClient(blobName);
 | |
|             return await blobClient.UploadAsync(content, overwrite: true);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Downloads a blob from the container.
 | |
|         /// </summary>
 | |
|         public async Task<Response<BlobDownloadInfo>> DownloadBlobAsync(string blobName)
 | |
|         {
 | |
|             var blobClient = _blobContainerClient.GetBlobClient(blobName);
 | |
|             return await blobClient.DownloadAsync();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Deletes a blob from the container.
 | |
|         /// </summary>
 | |
|         public async Task<bool> DeleteBlobAsync(string blobName)
 | |
|         {
 | |
|             var blobClient = _blobContainerClient.GetBlobClient(blobName);
 | |
|             return await blobClient.DeleteIfExistsAsync();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Lists all blobs in the container with an optional prefix.
 | |
|         /// </summary>
 | |
|         public async Task<IEnumerable<BlobItem>> ListBlobItemAsync(string? prefix = null)
 | |
|         {
 | |
|             var blobs = new List<BlobItem>();
 | |
| 
 | |
|             await foreach (var blobItem in _blobContainerClient.GetBlobsAsync(prefix: prefix))
 | |
|             {
 | |
|                 blobs.Add(blobItem);
 | |
|             }
 | |
| 
 | |
|             return blobs;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Retrieves the account information for the associated Blob Service Client.
 | |
|         /// </summary>
 | |
|         /// <param name="cancellation">
 | |
|         /// A <see cref="CancellationToken"/> that can be used to cancel the operation.
 | |
|         /// </param>
 | |
|         /// <returns>
 | |
|         /// A task that represents the asynchronous operation. The task result contains the
 | |
|         /// <see cref="AccountInfo"/> object, which provides details about the account, such as the SKU
 | |
|         /// and account kind.
 | |
|         public async Task<AccountInfo> GetAccountInfoAsync(CancellationToken cancellation)
 | |
|         {
 | |
|             return await _blobServiceClient.GetAccountInfoAsync(cancellation);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Gets a blob client for a specific blob.
 | |
|         /// </summary>
 | |
|         public BlobClient GetBlobClient(string blobName)
 | |
|         {
 | |
|             return _blobContainerClient.GetBlobClient(blobName);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Lists blobs hierarchically using a delimiter.
 | |
|         /// </summary>
 | |
|         public async Task<IEnumerable<BlobHierarchyItem>> ListBlobsByHierarchyAsync(string? prefix = null, string delimiter = "/")
 | |
|         {
 | |
|             var blobs = new List<BlobHierarchyItem>();
 | |
| 
 | |
|             await foreach (var blobHierarchyItem in _blobContainerClient.GetBlobsByHierarchyAsync(prefix: prefix, delimiter: delimiter))
 | |
|             {
 | |
|                 blobs.Add(blobHierarchyItem);
 | |
|             }
 | |
| 
 | |
|             return blobs;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Generates a SAS token for the container with specified permissions.
 | |
|         /// </summary>
 | |
|         public Uri GenerateContainerSasUri(BlobContainerSasPermissions permissions, DateTimeOffset expiresOn)
 | |
|         {
 | |
|             if (!_blobContainerClient.CanGenerateSasUri)
 | |
|             {
 | |
|                 throw new InvalidOperationException("Cannot generate SAS URI. Ensure the client is authorized with account key credentials.");
 | |
|             }
 | |
| 
 | |
|             var sasBuilder = new BlobSasBuilder
 | |
|             {
 | |
|                 BlobContainerName = _blobContainerClient.Name,
 | |
|                 Resource = "c", // c for container
 | |
|                 ExpiresOn = expiresOn
 | |
|             };
 | |
| 
 | |
|             sasBuilder.SetPermissions(permissions);
 | |
|             return _blobContainerClient.GenerateSasUri(sasBuilder);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Acquires a lease on the blob container.
 | |
|         /// </summary>
 | |
|         public async Task<Response<BlobLease>> AcquireLeaseAsync(string? proposedId = null, TimeSpan? duration = null)
 | |
|         {
 | |
|             return await _blobContainerClient.GetBlobLeaseClient(proposedId).AcquireAsync(duration ?? TimeSpan.FromSeconds(60));
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Releases a lease on the blob container.
 | |
|         /// </summary>
 | |
|         public async Task ReleaseLeaseAsync(string leaseId)
 | |
|         {
 | |
|             await _blobContainerClient.GetBlobLeaseClient(leaseId).ReleaseAsync();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sets access policies for the blob container.
 | |
|         /// </summary>
 | |
|         public async Task SetAccessPolicyAsync(PublicAccessType accessType, IEnumerable<BlobSignedIdentifier>? identifiers = null)
 | |
|         {
 | |
|             await _blobContainerClient.SetAccessPolicyAsync(accessType, identifiers);
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Lists blobs in the container with an optional prefix.
 | |
|         /// </summary>
 | |
|         public async Task<IEnumerable<BlobFileAdapter>> ListBlobsAsync(string? prefix = null)
 | |
|         {
 | |
|             var blobs = new List<BlobFileAdapter>();
 | |
| 
 | |
|             await foreach (BlobItem blob in _blobContainerClient.GetBlobsAsync(prefix: prefix))
 | |
|             {
 | |
|                 blobs.Add(new BlobFileAdapter
 | |
|                 {
 | |
|                     Name = blob.Name,
 | |
|                     Uri = $"{_blobContainerClient.Uri}/{blob.Name}",
 | |
|                     ContentType = blob.Properties.ContentType,
 | |
|                     Size = blob.Properties.ContentLength,
 | |
|                     DateUpload = blob.Properties.LastModified?.UtcDateTime.ToString("yyyy-MM-dd HH:mm:ss"),
 | |
|                     ShortDate = blob.Properties.LastModified?.UtcDateTime.ToString("MM-dd-yyyy"),
 | |
|                     Status = "Available"
 | |
|                 });
 | |
|             }
 | |
| 
 | |
|             return blobs;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Uploads a blob to the container.
 | |
|         /// </summary>
 | |
|         public async Task<BlobFileAdapter> UploadBlobAsync(BlobAddDto newBlob)
 | |
|         {
 | |
|             var blobClient = _blobContainerClient.GetBlobClient(newBlob.FileName);
 | |
| 
 | |
|             using var stream = new MemoryStream(newBlob.FileContent);
 | |
|             await blobClient.UploadAsync(stream, overwrite: true);
 | |
| 
 | |
|             var properties = await blobClient.GetPropertiesAsync();
 | |
| 
 | |
|             return new BlobFileAdapter
 | |
|             {
 | |
|                 Name = newBlob.FileName ?? "",
 | |
|                 Uri = blobClient.Uri.ToString(),
 | |
|                 ContentType = properties.Value.ContentType,
 | |
|                 Size = properties.Value.ContentLength,
 | |
|                 DateUpload = properties.Value.LastModified.UtcDateTime.ToString("yyyy-MM-dd HH:mm:ss"),
 | |
|                 ShortDate = properties.Value.LastModified.UtcDateTime.ToString("MM-dd-yyyy"),
 | |
|                 Status = "Uploaded"
 | |
|             };
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Deletes a blob from the container.
 | |
|         /// </summary>
 | |
|         public async Task<BlobFileAdapter?> DeleteBlobsAsync(string fileName)
 | |
|         {
 | |
|             var blobClient = _blobContainerClient.GetBlobClient(fileName);
 | |
| 
 | |
|             if (await blobClient.ExistsAsync())
 | |
|             {
 | |
|                 var properties = await blobClient.GetPropertiesAsync();
 | |
|                 var _response = await blobClient.DeleteIfExistsAsync();
 | |
| 
 | |
|                 return new BlobFileAdapter
 | |
|                 {
 | |
|                     Name = fileName,
 | |
|                     Uri = blobClient.Uri.ToString(),
 | |
|                     ContentType = properties.Value.ContentType,
 | |
|                     Size = properties.Value.ContentLength,
 | |
|                     DateUpload = properties.Value.LastModified.UtcDateTime.ToString("yyyy-MM-dd HH:mm:ss"),
 | |
|                     ShortDate = properties.Value.LastModified.UtcDateTime.ToString("MM-dd-yyyy"),
 | |
|                     Status = _response ? "Deleted" : "Failed to delete"
 | |
|                 };
 | |
|             }
 | |
| 
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Downloads a blob's content.
 | |
|         /// </summary>
 | |
|         public async Task<BlobDownloadInfo> DownloadBlobsAsync(string blobName)
 | |
|         {
 | |
|             var blobClient = _blobContainerClient.GetBlobClient(blobName);
 | |
| 
 | |
|             if (!await blobClient.ExistsAsync())
 | |
|             {
 | |
|                 throw new FileNotFoundException($"Blob '{blobName}' does not exist in the container '{_containerName}'.");
 | |
|             }
 | |
| 
 | |
|             return await blobClient.DownloadAsync();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Generates a secure download URI for a specified blob in the storage container.
 | |
|         /// </summary>
 | |
|         /// <param name="blobName">The name of the blob for which the download URI is being generated.</param>
 | |
|         /// <returns>
 | |
|         /// An instance of <see cref="BlobDownloadUriAdapter"/> containing the generated URI, blob name, and status,
 | |
|         /// or <c>null</c> if the blob does not exist.
 | |
|         /// </returns>
 | |
|         /// <remarks>
 | |
|         /// The generated URI includes a Shared Access Signature (SAS) token, which allows secure, time-limited access to the blob.
 | |
|         /// The SAS token grants read-only access to the blob for a duration of 5 minutes starting from the current time.
 | |
|         /// </remarks>
 | |
|         /// <exception cref="ArgumentNullException">Thrown if <paramref name="blobName"/> is null or empty.</exception>
 | |
|         /// <exception cref="StorageException">Thrown if there is an issue communicating with the Azure Blob service.</exception>
 | |
|         public async ValueTask<BlobDownloadUriAdapter?> GenerateBlobDownloadUri(string blobName)
 | |
|         {
 | |
|             if (string.IsNullOrWhiteSpace(blobName))
 | |
|                 throw new ArgumentNullException(nameof(blobName), "Blob name cannot be null or empty.");
 | |
| 
 | |
|             var blob = _blobContainerClient.GetBlobClient(blobName);
 | |
| 
 | |
|             if (!await blob.ExistsAsync())
 | |
|                 return null;
 | |
| 
 | |
|             var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? string.Empty;
 | |
| 
 | |
|             if (environment == "Local")
 | |
|             {
 | |
|                 return GenerateDownloadUri(blob);
 | |
|             }
 | |
| 
 | |
|             var delegationKey = await _blobServiceClient.GetUserDelegationKeyAsync(
 | |
|                 DateTimeOffset.UtcNow,
 | |
|                 DateTimeOffset.UtcNow.AddHours(2));
 | |
| 
 | |
|             var sasBuilder = new BlobSasBuilder
 | |
|             {
 | |
|                 BlobContainerName = blob.BlobContainerName,
 | |
|                 BlobName = blob.Name,
 | |
|                 Resource = "b",
 | |
|                 StartsOn = DateTimeOffset.UtcNow,
 | |
|                 ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(5)
 | |
|             };
 | |
|             sasBuilder.SetPermissions(BlobSasPermissions.Read);
 | |
|             sasBuilder.Protocol = SasProtocol.Https;
 | |
| 
 | |
|             var blobUriBuilder = new BlobUriBuilder(blob.Uri)
 | |
|             {
 | |
|                 Sas = sasBuilder.ToSasQueryParameters(delegationKey, _blobServiceClient.AccountName)
 | |
|             };
 | |
| 
 | |
|             return new BlobDownloadUriAdapter
 | |
|             {
 | |
|                 Uri = blobUriBuilder.ToUri(),
 | |
|                 Name = blob.Name,
 | |
|                 Status = "Available"
 | |
|             };
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Generates a download URI for a blob using a Shared Access Signature in local (Azurite) environment.
 | |
|         /// </summary>
 | |
|         /// <param name="blob">The blob client for which the URI is being generated.</param>
 | |
|         /// <returns>An instance of <see cref="BlobDownloadUriAdapter"/> containing the SAS URI and metadata.</returns>
 | |
|         private BlobDownloadUriAdapter GenerateDownloadUri(BlobClient blob)
 | |
|         {
 | |
|             var sasBuilder = new BlobSasBuilder
 | |
|             {
 | |
|                 BlobContainerName = blob.BlobContainerName,
 | |
|                 BlobName = blob.Name,
 | |
|                 Resource = "b",
 | |
|                 StartsOn = DateTimeOffset.UtcNow,
 | |
|                 ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(5)
 | |
|             };
 | |
|             sasBuilder.SetPermissions(BlobSasPermissions.Read);
 | |
|             sasBuilder.Protocol = SasProtocol.HttpsAndHttp;
 | |
| 
 | |
|             var accountName = _configuration["BlobStorage:AccountName"];
 | |
|             var accountKey = _configuration["BlobStorage:AccountKey"];
 | |
| 
 | |
|             var storageCredentials = new StorageSharedKeyCredential(accountName, accountKey);
 | |
|             var sasToken = sasBuilder.ToSasQueryParameters(storageCredentials);
 | |
| 
 | |
|             var blobUriBuilder = new BlobUriBuilder(blob.Uri)
 | |
|             {
 | |
|                 Sas = sasToken
 | |
|             };
 | |
| 
 | |
|             return new BlobDownloadUriAdapter
 | |
|             {
 | |
|                 Uri = blobUriBuilder.ToUri(),
 | |
|                 Name = blob.Name,
 | |
|                 Status = "Available"
 | |
|             };
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Retrieves the hierarchical folder structure.
 | |
|         /// </summary>
 | |
|         public async Task<List<BlobStorageFolder>> GetFolderHierarchyAsync(string prefix)
 | |
|         {
 | |
|             var rootFolder = new BlobStorageFolder { Name = prefix };
 | |
|             await PopulateFolderAsync(rootFolder, prefix);
 | |
|             return new List<BlobStorageFolder> { rootFolder };
 | |
|         }
 | |
| 
 | |
|         private async Task PopulateFolderAsync(BlobStorageFolder folder, string? prefix)
 | |
|         {
 | |
|             await foreach (var blobHierarchy in _blobContainerClient.GetBlobsByHierarchyAsync(prefix: prefix, delimiter: "/"))
 | |
|             {
 | |
|                 if (blobHierarchy.IsPrefix)
 | |
|                 {
 | |
|                     var subFolder = new BlobStorageFolder { Name = blobHierarchy.Prefix.TrimEnd('/') };
 | |
|                     folder.SubFolders.Add(subFolder);
 | |
|                     await PopulateFolderAsync(subFolder, blobHierarchy.Prefix);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     folder.Files.Add(new BlobStorageFilesAdapter(Content: blobHierarchy.Prefix,  //Fix
 | |
|                                                                 Name: blobHierarchy.Blob.Name,
 | |
|                                                                 ContentType: "",
 | |
|                                                                 DownloadUrl: $"{_blobContainerClient.Uri}/{blobHierarchy.Blob.Name}"));
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         public async Task<Dictionary<string, List<string>>> ListNeighborFoldersAsync(string? prefix)
 | |
|         {
 | |
|             await ListFoldersInTrieAsync(prefix);
 | |
| 
 | |
|             var groupedFolders = _trie.SearchByPrefix(prefix)
 | |
|                 .OrderBy(folder => folder)
 | |
|                 .GroupBy(folder => folder.Substring(0, 1))
 | |
|                 .ToDictionary(group => group.Key, group => group.ToList());
 | |
| 
 | |
|             return groupedFolders;
 | |
|         }
 | |
|         private async Task ListFoldersInTrieAsync(string? prefix)
 | |
|         {
 | |
|             await foreach (var blobHierarchy in _blobContainerClient.GetBlobsByHierarchyAsync(prefix: prefix, delimiter: "/"))
 | |
|             {
 | |
|                 if (blobHierarchy.IsPrefix)
 | |
|                 {
 | |
|                     var folderName = blobHierarchy.Prefix.TrimEnd('/').Split('/').Last();
 | |
|                     _trie.Insert(folderName);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | 
