using System.Text; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Blob; using OpenHarbor.Storage.Abstractions; namespace OpenHarbor.Storage.Azure.Blob; public class AzureBlobStorageProvider : IStorageProvider { private string? _connectionString = null; private string? _containerName = null; public AzureBlobStorageProvider(string connectionString, string containerName) { SetConnectionString(connectionString); SetContainerName(containerName); } public void SetContainerName(string name) { _containerName = name; } public void SetConnectionString(string connectionString) { _connectionString = connectionString; } public Task CreateDirectoryAsync(string path, CancellationToken cancellationToken) { var ret = new AzureBlobNotExistingDirectoryInfo(path); return Task.FromResult(ret); } public async Task DeleteDirectoryAsync(string path, bool force = false, CancellationToken cancellationToken = default) { var ret = new List(); var container = GetContainer(); var finalPath = CleanDirectoryPath(path); BlobContinuationToken? continuationToken = null; List results = new List(); do { BlobResultSegment response; if (continuationToken == null) response = await container.ListBlobsSegmentedAsync(finalPath, true, BlobListingDetails.All, null, continuationToken, null, null, cancellationToken); else response = await container.ListBlobsSegmentedAsync(continuationToken); continuationToken = response.ContinuationToken; results.AddRange(response.Results); } while (continuationToken != null); var files = results.Where(t => t is CloudBlockBlob).Cast().ToList(); foreach (var file in files) await this.DeleteFileAsync(file.Name, cancellationToken); } public Task DeleteFileAsync(string path, CancellationToken cancellationToken) { return GetContainer() .GetBlobReference(path) .DeleteIfExistsAsync(DeleteSnapshotsOption.None, null, null, null, cancellationToken); } public Task FileExistsAsync(string path, CancellationToken cancellationToken) => GetContainer() .GetBlobReference(path) .ExistsAsync(null, null, cancellationToken); public async Task> GetDirectoriesAsync(string path, CancellationToken cancellationToken) => (await GetListAsync(path, cancellationToken)) .Where(t => t.IsDirectory) .Cast() .ToList(); public async Task> GetFilesAsync(string path, string? pattern = null, SearchOption searchOption = SearchOption.TopDirectoryOnly, CancellationToken cancellationToken = default) { if (pattern != null) throw new NotSupportedException("Blob Storage does not support glob searching only prefix."); var result = await GetListAsync(path, cancellationToken); return result .Where(file => false == file.IsDirectory) .Cast() .ToList(); } private static string? CleanDirectoryPath(string? path) { if (path == null) return null; path = path.TrimEnd('/'); if (path != "") path += "/"; return path; } private CloudBlobContainer GetContainer() { var account = CloudStorageAccount.Parse(_connectionString); var client = account.CreateCloudBlobClient(); var container = client.GetContainerReference(_containerName); return container; } public async Task> GetListAsync(string path, CancellationToken cancellationToken) { var ret = new List(); var container = GetContainer(); var finalPath = CleanDirectoryPath(path); BlobContinuationToken? continuationToken = null; var results = new List(); do { BlobResultSegment response; if (continuationToken == null) response = await container.ListBlobsSegmentedAsync(finalPath, false, BlobListingDetails.None, new int?(), continuationToken, null, null, cancellationToken); else response = await container.ListBlobsSegmentedAsync(continuationToken); continuationToken = response.ContinuationToken; results.AddRange(response.Results); } while (continuationToken != null); foreach (var result in results) { if (result is CloudBlobDirectory blobDirectory) ret.Add(new AzureBlobDirectoryInfo(blobDirectory)); else if (result is CloudBlockBlob blobBlock) ret.Add(new AzureBlobFileInfo(blobBlock)); } return ret; } public Task WriteFileAsync(string sourcePath, string path, bool overrideIfExists = true, CancellationToken cancellationToken = default) { return WriteFileAsync(sourcePath, path, new DefaultWriteOptions { OverrideIfExists = overrideIfExists }, cancellationToken); } public Task WriteFileAsync(byte[] bytes, string path, bool overrideIfExists = true, CancellationToken cancellationToken = default) { return WriteFileAsync(bytes, path, new DefaultWriteOptions { OverrideIfExists = overrideIfExists }, cancellationToken); } public Task WriteFileAsync(Stream stream, string path, bool overrideIfExists = true, CancellationToken cancellationToken = default) { return WriteFileAsync(stream, path, new DefaultWriteOptions { OverrideIfExists = overrideIfExists }, cancellationToken); } public async Task GetFileStreamAsync(string path, CancellationToken cancellationToken) { await ThrowNotExistingAsync(path, cancellationToken); var container = GetContainer(); var blob = container.GetBlockBlobReference(path); return await blob.OpenReadAsync(null, null, null, cancellationToken); } private async Task ThrowNotExistingAsync(string path, CancellationToken cancellationToken) { if (false == await FileExistsAsync(path, cancellationToken)) throw new FileDoesNotExistException(path); } public async Task GetFileBytesAsync(string path, CancellationToken cancellationToken) { await ThrowNotExistingAsync(path, cancellationToken); var container = GetContainer(); var blob = container.GetBlockBlobReference(path); var bytes = new byte[blob.Properties.Length]; await blob.DownloadToByteArrayAsync(bytes, 0, null, null, null, cancellationToken); return bytes; } public async Task GetFileContentAsync(string path, Encoding encoding, CancellationToken cancellationToken) { await ThrowNotExistingAsync(path, cancellationToken); return encoding.GetString(await GetFileBytesAsync(path, cancellationToken)); } public bool IsFileNameAllowed(string fileName) => true; public string SanitizeFileName(string key, string replacement) => key; public async Task WriteFileAsync(string sourcePath, string path, IWriteFileOptions options, CancellationToken cancellationToken) { if (options is null) throw new ArgumentNullException(nameof(options)); if (!options.OverrideIfExists && await FileExistsAsync(path, cancellationToken)) throw new FileAlreadyExistsException(path); var container = GetContainer(); var blob = container.GetBlockBlobReference(path); await blob.UploadFromFileAsync(sourcePath, null, null, null, cancellationToken); return new AzureBlobFileInfo(blob); } public async Task WriteFileAsync(byte[] bytes, string path, IWriteFileOptions options, CancellationToken cancellationToken) { if (options is null) throw new ArgumentNullException(nameof(options)); if (!options.OverrideIfExists && await FileExistsAsync(path, cancellationToken)) throw new FileAlreadyExistsException(path); var container = GetContainer(); var blob = container.GetBlockBlobReference(path); await blob.UploadFromByteArrayAsync(bytes, 0, bytes.Length, null, null, null, cancellationToken); return new AzureBlobFileInfo(blob); } public async Task WriteFileAsync(Stream stream, string path, IWriteFileOptions options, CancellationToken cancellationToken) { if (options is null) throw new ArgumentNullException(nameof(options)); if (!options.OverrideIfExists && await FileExistsAsync(path, cancellationToken)) throw new FileAlreadyExistsException(path); if (stream.CanSeek && stream.Position != 0) stream.Seek(0, SeekOrigin.Begin); var container = GetContainer(); var blob = container.GetBlockBlobReference(path); await blob.UploadFromStreamAsync(stream, null, null, null, cancellationToken); return new AzureBlobFileInfo(blob); } }