diff --git a/src/QMS.Storage.AzureStorage/AzureStorageService.cs b/src/QMS.Storage.AzureStorage/AzureStorageService.cs index 6db45a6..4b2630e 100644 --- a/src/QMS.Storage.AzureStorage/AzureStorageService.cs +++ b/src/QMS.Storage.AzureStorage/AzureStorageService.cs @@ -27,7 +27,7 @@ public AzureStorageService(IOptions config) /// /// Store the given fileData in the blobstorage and return a blob identifying the results /// - public async Task StoreFileAsync(byte[] fileData, string contentType, string? fileName = null, string? containerName = null) + public async Task StoreFileAsync(byte[] fileData, string contentType, string? fileName = null, string? containerName = null, string? leaseId = null) { var blobContainer = await GetBlobContainer(containerName).ConfigureAwait(false); if (string.IsNullOrEmpty(fileName)) @@ -40,7 +40,10 @@ public async Task StoreFileAsync(byte[] fileData, string contentType using (var stream = new MemoryStream(fileData, writable: false)) { - await blockBlob.UploadFromStreamAsync(stream).ConfigureAwait(false); + if(leaseId != null) + await blockBlob.UploadFromStreamAsync(stream, new AccessCondition { LeaseId = leaseId }, null, null).ConfigureAwait(false); + else + await blockBlob.UploadFromStreamAsync(stream).ConfigureAwait(false); } return blockBlob; @@ -107,15 +110,19 @@ public async Task GetFileReference(string blobStoreId, string? conta throw new InvalidDataException(); } - return blobReference; } - public async Task ReadFileAsJson(string fileName) where T : class + public async Task<(T?, string? leaseId)> ReadFileAsJson(string fileName, TimeSpan? leaseTime = null) where T : class { + string? leaseId = null; + ICloudBlob? blob = null; try { - var blob = await GetFileReference(fileName).ConfigureAwait(false); + blob = await GetFileReference(fileName).ConfigureAwait(false); + + if (leaseTime.HasValue) + leaseId = await blob.AcquireLeaseAsync(leaseTime); using (var stream = new MemoryStream()) { @@ -127,22 +134,27 @@ public async Task GetFileReference(string blobStoreId, string? conta var cmsItem = JsonSerializer.Deserialize(json); - return cmsItem; + return (cmsItem, leaseId); } } catch (FileNotFoundException) { return default; } + finally + { + if (leaseId != null && blob != null) + await blob.ReleaseLeaseAsync(new AccessCondition { LeaseId = leaseId }); + } } - public Task WriteFileAsJson(T item, string fileName) + public Task WriteFileAsJson(T item, string fileName, string? leaseId = null) { var json = JsonSerializer.Serialize(item); byte[] fileData = Encoding.ASCII.GetBytes(json); - return StoreFileAsync(fileData, "application/json", fileName); + return StoreFileAsync(fileData, "application/json", fileName, leaseId: leaseId); } public async Task> GetFilesFromDirectory(string path, string? containerName = null) diff --git a/src/QMS.Storage.AzureStorage/CmsItemStorageService.cs b/src/QMS.Storage.AzureStorage/CmsItemStorageService.cs index afbde30..029ac14 100644 --- a/src/QMS.Storage.AzureStorage/CmsItemStorageService.cs +++ b/src/QMS.Storage.AzureStorage/CmsItemStorageService.cs @@ -1,4 +1,5 @@ -using Microsoft.Azure.Storage.Blob; +using Microsoft.Azure.Storage; +using Microsoft.Azure.Storage.Blob; using Microsoft.Extensions.Options; using QMS.Models; using QMS.Storage.Interfaces; @@ -16,6 +17,7 @@ public class CmsItemStorageService : IReadCmsItem, IWriteCmsItem { private readonly AzureStorageService azureStorageService; private readonly CmsConfiguration cmsConfiguration; + private const string INDEX_FILE_NAME = "_index"; public bool CanSort => true; @@ -28,10 +30,10 @@ public CmsItemStorageService(AzureStorageService azureStorageService, IOptions results, int total)> List(string cmsType, string? sortField, string? sortOrder, int pageSize = 20, int pageIndex = 0) { //Get index file - var indexFileName = GenerateFileName(cmsType, "_index", null); + var indexFileName = GenerateFileName(cmsType, INDEX_FILE_NAME, null); //Get current index file - var indexFile = await azureStorageService.ReadFileAsJson>(indexFileName).ConfigureAwait(false); + var (indexFile, _) = await azureStorageService.ReadFileAsJson>(indexFileName).ConfigureAwait(false); indexFile = indexFile ?? new List(); var returnItems = indexFile.AsQueryable(); @@ -68,11 +70,13 @@ public CmsItemStorageService(AzureStorageService azureStorageService, IOptions Read(string cmsType, Guid id, string? lang) + public async Task Read(string cmsType, Guid id, string? lang) { var fileName = GenerateFileName(cmsType, id, lang); - return azureStorageService.ReadFileAsJson(fileName); + var (indexFile, _) = await azureStorageService.ReadFileAsJson(fileName); + + return indexFile; } public async Task Write(CmsItem item, string cmsType, Guid id, string? lang) @@ -81,34 +85,49 @@ public async Task Write(CmsItem item, string cmsType, Guid id, string? lang) await azureStorageService.WriteFileAsJson(item, fileName); //Write index file for paging and sorting - var indexFileName = GenerateFileName(cmsType, "_index", lang); + var indexFileName = GenerateFileName(cmsType, INDEX_FILE_NAME, lang); var typeInfo = cmsConfiguration.Entities.Where(x => x.Key == cmsType).FirstOrDefault(); + string? leaseId = null; if (typeInfo == null) return; - //Get current index file - var indexFile = await azureStorageService.ReadFileAsJson>(indexFileName).ConfigureAwait(false); - indexFile = indexFile ?? new List(); + try + { + //Get current index file + var (indexFile, newLeaseId) = await azureStorageService.ReadFileAsJson>(indexFileName, TimeSpan.FromSeconds(20)).ConfigureAwait(false); + indexFile = indexFile ?? new List(); + leaseId = newLeaseId; - //Remove existing item - indexFile.Remove(indexFile.Where(x => x.Id == item.Id).FirstOrDefault()); + //Remove existing item + indexFile.Remove(indexFile.Where(x => x.Id == item.Id).FirstOrDefault()); - var indexItem = new CmsItem { - Id = id, - CmsType = cmsType, - LastModifiedDate = item.LastModifiedDate - }; + var indexItem = new CmsItem + { + Id = id, + CmsType = cmsType, + LastModifiedDate = item.LastModifiedDate + }; - foreach (var prop in typeInfo.ListViewProperties) - { - var value = item.AdditionalProperties[prop.Key]; - indexItem.AdditionalProperties[prop.Key] = value; - } + foreach (var prop in typeInfo.ListViewProperties) + { + var value = item.AdditionalProperties[prop.Key]; + indexItem.AdditionalProperties[prop.Key] = value; + } - indexFile.Add(indexItem); + indexFile.Add(indexItem); - await azureStorageService.WriteFileAsJson(indexFile, indexFileName); + await azureStorageService.WriteFileAsJson(indexFile, indexFileName, leaseId); + } + finally + { + //Release lease + if (leaseId != null) + { + var file = await azureStorageService.GetFileReference(indexFileName); + await file.ReleaseLeaseAsync(new AccessCondition { LeaseId = leaseId }); + } + } } @@ -130,14 +149,32 @@ public async Task Delete(string cmsType, Guid id, string? lang) } //Write index file for paging and sorting - var indexFileName = GenerateFileName(cmsType, "_index", lang); - //Get current index file - var indexFile = await azureStorageService.ReadFileAsJson>(indexFileName).ConfigureAwait(false); - indexFile = indexFile ?? new List(); + var indexFileName = GenerateFileName(cmsType, INDEX_FILE_NAME, lang); + string? leaseId = null; - //Remove existing item - indexFile.Remove(indexFile.Where(x => x.Id == id).FirstOrDefault()); - await azureStorageService.WriteFileAsJson(indexFile, indexFileName); + try + { + //Get current index file + var (indexFile, newLeaseId) = await azureStorageService.ReadFileAsJson>(indexFileName, TimeSpan.FromSeconds(20)).ConfigureAwait(false); + indexFile = indexFile ?? new List(); + leaseId = newLeaseId; + + //Remove existing item + indexFile.Remove(indexFile.Where(x => x.Id == id).FirstOrDefault()); + await azureStorageService.WriteFileAsJson(indexFile, indexFileName); + } + finally + { + //Release lease + if (leaseId != null) + { + var file = await azureStorageService.GetFileReference(indexFileName); + await file.ReleaseLeaseAsync(new AccessCondition + { + LeaseId = leaseId + }); + } + } } private static string GenerateFileName(string cmsType, Guid id, string? lang)