diff --git a/az-appservice-dotnet/Program.cs b/az-appservice-dotnet/Program.cs index 71e8bc2..90e79a2 100644 --- a/az-appservice-dotnet/Program.cs +++ b/az-appservice-dotnet/Program.cs @@ -27,7 +27,8 @@ public static async Task Main(string[] args) builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/az-appservice-dotnet/az-appservice-dotnet.csproj b/az-appservice-dotnet/az-appservice-dotnet.csproj index 6216f41..7dafce8 100644 --- a/az-appservice-dotnet/az-appservice-dotnet.csproj +++ b/az-appservice-dotnet/az-appservice-dotnet.csproj @@ -14,5 +14,6 @@ + diff --git a/az-appservice-dotnet/providers/Azure/v1/CosmosDbPersistProcessingStateProvider.cs b/az-appservice-dotnet/providers/Azure/v1/CosmosDbPersistProcessingStateProvider.cs index 8ad2bc3..5690b0f 100644 --- a/az-appservice-dotnet/providers/Azure/v1/CosmosDbPersistProcessingStateProvider.cs +++ b/az-appservice-dotnet/providers/Azure/v1/CosmosDbPersistProcessingStateProvider.cs @@ -1,7 +1,5 @@ -using System.Collections.Concurrent; using System.Collections.Immutable; using System.Runtime.CompilerServices; -using az_appservice_dotnet.services; using az_appservice_dotnet.services.v1.State; using az_appservice_dotnet.services.v1.State.dependencies; using Microsoft.Azure.Cosmos; @@ -98,9 +96,11 @@ internal CosmosDbPersistProcessingStateProvider(Container container) public Task CreateStateAsync(in IProcessingStateService.State state) { + var cs = new CosmosState(Guid.NewGuid().ToString(), state); + Console.WriteLine( + $"State({cs.Id}) change requested {state.Status}: file={state.FileName}, originalUrl={state.OriginalFileUrl}, processedUrl={state.ProcessedFileUrl}"); return - _container.CreateItemAsync( - new CosmosState(Guid.NewGuid().ToString(), state), new PartitionKey(state.TaskId)) + _container.CreateItemAsync(cs, new PartitionKey(state.TaskId)) .ContinueWith(cs => (IProcessingStateService.State)cs.Result.Resource); } @@ -119,6 +119,8 @@ public IProcessingStateService.State ReadStateAsync(IProcessingStateService.Stat public Task UpdateStateAsync(in IProcessingStateService.State state) { + Console.WriteLine( + $"State({(string)state.Id}) change requested {state.Status}: file={state.FileName}, originalUrl={state.OriginalFileUrl}, processedUrl={state.ProcessedFileUrl}"); return _container.ReplaceItemAsync(state, state.Id, new PartitionKey(state.TaskId)) .ContinueWith(t => (IProcessingStateService.State)t.Result.Resource); } diff --git a/az-appservice-dotnet/routing/v1/RouteGroupBuilderImages.cs b/az-appservice-dotnet/routing/v1/RouteGroupBuilderImages.cs index b3c650c..d6bfb58 100644 --- a/az-appservice-dotnet/routing/v1/RouteGroupBuilderImages.cs +++ b/az-appservice-dotnet/routing/v1/RouteGroupBuilderImages.cs @@ -37,6 +37,7 @@ private static IResult StartProcessingFromFake(HttpContext context, IFileProvide { uint.TryParse(context.Request.Query["size"].ToString(), out var size); if (size == 0) size = 10; + if (size > 100) size = 100; var name = context.Request.Query["name"].ToString(); if (string.IsNullOrEmpty(name)) diff --git a/az-appservice-dotnet/services/v1/ImageProcessing/IImageProcessorService.cs b/az-appservice-dotnet/services/v1/ImageProcessing/IImageProcessorService.cs index 9839a44..fb6a324 100644 --- a/az-appservice-dotnet/services/v1/ImageProcessing/IImageProcessorService.cs +++ b/az-appservice-dotnet/services/v1/ImageProcessing/IImageProcessorService.cs @@ -3,4 +3,6 @@ namespace az_appservice_dotnet.services.v1.ImageProcessing; public interface IImageProcessorService { Task ProcessImageAsync(string imageFilePath); + bool CanProcessImage(string imageFilePath); + string[] SupportedFormats { get; } } \ No newline at end of file diff --git a/az-appservice-dotnet/services/v1/ImageProcessing/NullImageProcessorService.cs b/az-appservice-dotnet/services/v1/ImageProcessing/NullImageProcessorService.cs index 62b794a..40a0959 100644 --- a/az-appservice-dotnet/services/v1/ImageProcessing/NullImageProcessorService.cs +++ b/az-appservice-dotnet/services/v1/ImageProcessing/NullImageProcessorService.cs @@ -1,6 +1,6 @@ namespace az_appservice_dotnet.services.v1.ImageProcessing; -public class NullImageProcessorService: IImageProcessorService +public class NullImageProcessorService : IImageProcessorService { public Task ProcessImageAsync(string imageFilePath) { @@ -9,13 +9,27 @@ public Task ProcessImageAsync(string imageFilePath) throw new FileNotFoundException($"NullImageProcessorService: File not found: {imageFilePath}"); } + var fileInfo = new FileInfo(imageFilePath); + if (fileInfo.Length > 100 * 1024 * 1024) + { + throw new FileLoadException($"NullImageProcessorService: File too large: {imageFilePath}"); + } + return Task.Run(() => { - // get tmp file path var tmpFilePath = Path.GetTempFileName(); - // copy imageFilePath to tmpFilePath File.Copy(imageFilePath, tmpFilePath, true); return tmpFilePath; }); } + + public bool CanProcessImage(string imageFilePath) + { + return true; + } + + public string[] SupportedFormats + { + get { return new[] { "*" }; } + } } \ No newline at end of file diff --git a/az-appservice-dotnet/services/v1/ImageProcessing/UpsideDownImageProcesserService.cs b/az-appservice-dotnet/services/v1/ImageProcessing/UpsideDownImageProcesserService.cs new file mode 100644 index 0000000..1acb5d6 --- /dev/null +++ b/az-appservice-dotnet/services/v1/ImageProcessing/UpsideDownImageProcesserService.cs @@ -0,0 +1,40 @@ +namespace az_appservice_dotnet.services.v1.ImageProcessing; + +public class UpsideDownImageProcessorService : IImageProcessorService +{ + public Task ProcessImageAsync(string imageFilePath) + { + if (!File.Exists(imageFilePath)) + { + throw new FileNotFoundException($"UpsideDownImageProcessorService: File not found: {imageFilePath}"); + } + + var fileInfo = new FileInfo(imageFilePath); + if (fileInfo.Length > 100 * 1024 * 1024) + { + throw new FileLoadException($"UpsideDownImageProcessorService: File too large: {imageFilePath}"); + } + + return Task.Run(() => + { + using (Image image = Image.Load(imageFilePath)) + { + image.Mutate(x => x.Flip(FlipMode.Vertical)); + var tmpFilePath = Path.GetTempFileName() + ".jpg"; + image.SaveAsJpeg(tmpFilePath); + return tmpFilePath; + } + }); + } + + public bool CanProcessImage(string imageFilePath) + { + return imageFilePath.EndsWith(".jpg", StringComparison.InvariantCultureIgnoreCase) || + imageFilePath.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase); + } + + public string[] SupportedFormats + { + get { return new[] { "*.jpg", "*.png" }; } + } +} \ No newline at end of file diff --git a/az-appservice-dotnet/services/v1/Monitor/ConsoleMonitor.cs b/az-appservice-dotnet/services/v1/Monitor/ConsoleMonitor.cs index 0b9f168..9a74e6b 100644 --- a/az-appservice-dotnet/services/v1/Monitor/ConsoleMonitor.cs +++ b/az-appservice-dotnet/services/v1/Monitor/ConsoleMonitor.cs @@ -15,7 +15,7 @@ public void StartStateMonitor() { _processingStateService.ListenToStateChanges(state => { - Console.WriteLine($"State changed to {state.Status}: file={state.FileName}, originalUrl={state.OriginalFileUrl}, processedUrl={state.ProcessedFileUrl}"); + Console.WriteLine($"State({(string)state.Id}) change received {state.Status}: file={state.FileName}, originalUrl={state.OriginalFileUrl}, processedUrl={state.ProcessedFileUrl}"); }); } } \ No newline at end of file diff --git a/az-appservice-dotnet/services/v1/ProcessorService.cs b/az-appservice-dotnet/services/v1/ProcessorService.cs index 6f4f9ec..2572da9 100644 --- a/az-appservice-dotnet/services/v1/ProcessorService.cs +++ b/az-appservice-dotnet/services/v1/ProcessorService.cs @@ -9,28 +9,46 @@ public class ProcessorService private IBlobService _blobService; private IProcessingStateService _processingStateService; private IImageProcessorService _imageProcessorService; + private IImageProcessorService _nullImageProcessorService; public ProcessorService(IBlobService blobService, IProcessingStateService processingStateService, - IImageProcessorService imageProcessorService) + IImageProcessorService imageProcessorService, NullImageProcessorService nullImageProcessorService) { _imageProcessorService = imageProcessorService; _blobService = blobService; _processingStateService = processingStateService; + _nullImageProcessorService = nullImageProcessorService; } public void StartWaitForImagesToProcess() { + // TODO: see https://www.jetbrains.com/help/resharper/AsyncVoidLambda.html + // ReSharper disable once AsyncVoidLambda _processingStateService.ListenToStateChanges(async state => { if (state.Status == IProcessingStateService.Status.WaitingForProcessing) { Console.WriteLine(state.OriginalFileUrl); - var tmpFilePath = Path.GetTempFileName(); + IImageProcessorService processor; + if (_imageProcessorService.CanProcessImage(state.FileName)) + { + processor = _imageProcessorService; + } + else + { + var supported = string.Join(", ", _imageProcessorService.SupportedFormats); + Console.WriteLine( + $"{state.FileName} is not supported (only {supported} are allowed). Using NullImageProcessorService."); + processor = _nullImageProcessorService; + } + var stateProcessing = await _processingStateService.MoveToProcessingStateAsync(state); + // add filename to keep extension + var tmpFilePath = Path.GetTempFileName() + state.FileName; try { - var ok = await _blobService.DownloadBlobAsync(state.FileName, tmpFilePath); - var processedFilePath = await _imageProcessorService.ProcessImageAsync(tmpFilePath); + await _blobService.DownloadBlobAsync(state.FileName, tmpFilePath); + var processedFilePath = await processor.ProcessImageAsync(tmpFilePath); var uploadedUri = await _blobService.UploadBlobAsync($"processed-{state.FileName}", processedFilePath); await _processingStateService.MoveToCompletedStateAsync(stateProcessing, uploadedUri.ToString()); diff --git a/az-appservice-dotnet/services/v1/ProducerService.cs b/az-appservice-dotnet/services/v1/ProducerService.cs index adadf68..0bab803 100644 --- a/az-appservice-dotnet/services/v1/ProducerService.cs +++ b/az-appservice-dotnet/services/v1/ProducerService.cs @@ -39,7 +39,6 @@ public void StartProcessImage(IFileProviderService.FileObject fileObject) try { var blobUri = await _blobService.UploadBlobAsync(fileObject.Name, fileObject.Path); - // TODO: not sure is it necessary to await the last call await _processingStateService.MoveToWaitingForProcessingStateAsync(state2, blobUri.ToString()); } catch (Exception e)