Skip to content

Commit

Permalink
Add Image ProcessorService.cs
Browse files Browse the repository at this point in the history
  • Loading branch information
dsame committed Nov 14, 2023
1 parent 878273c commit a464711
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 14 deletions.
3 changes: 2 additions & 1 deletion az-appservice-dotnet/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public static async Task Main(string[] args)
builder.Services.AddSingleton<IBlobService, BlobService>();
builder.Services.AddSingleton<IProcessingStateService, ProcessingStateService>();
builder.Services.AddSingleton<IStateMonitor, ConsoleMonitor>();
builder.Services.AddSingleton<IImageProcessorService, NullImageProcessorService>();
builder.Services.AddSingleton<IImageProcessorService, UpsideDownImageProcessorService>();
builder.Services.AddSingleton<NullImageProcessorService>();

builder.Services.AddSingleton<ProducerService>();
builder.Services.AddSingleton<ProcessorService>();
Expand Down
1 change: 1 addition & 0 deletions az-appservice-dotnet/az-appservice-dotnet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.16.2" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.19.0" />
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.36.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.2" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -98,9 +96,11 @@ internal CosmosDbPersistProcessingStateProvider(Container container)

public Task<IProcessingStateService.State> 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);
}

Expand All @@ -119,6 +119,8 @@ public IProcessingStateService.State ReadStateAsync(IProcessingStateService.Stat

public Task<IProcessingStateService.State> 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<CosmosState>(state, state.Id, new PartitionKey(state.TaskId))
.ContinueWith(t => (IProcessingStateService.State)t.Result.Resource);
}
Expand Down
1 change: 1 addition & 0 deletions az-appservice-dotnet/routing/v1/RouteGroupBuilderImages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ namespace az_appservice_dotnet.services.v1.ImageProcessing;
public interface IImageProcessorService
{
Task<string> ProcessImageAsync(string imageFilePath);
bool CanProcessImage(string imageFilePath);
string[] SupportedFormats { get; }
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace az_appservice_dotnet.services.v1.ImageProcessing;

public class NullImageProcessorService: IImageProcessorService
public class NullImageProcessorService : IImageProcessorService
{
public Task<string> ProcessImageAsync(string imageFilePath)
{
Expand All @@ -9,13 +9,27 @@ public Task<string> 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[] { "*" }; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace az_appservice_dotnet.services.v1.ImageProcessing;

public class UpsideDownImageProcessorService : IImageProcessorService
{
public Task<string> 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" }; }
}
}
2 changes: 1 addition & 1 deletion az-appservice-dotnet/services/v1/Monitor/ConsoleMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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}");
});
}
}
26 changes: 22 additions & 4 deletions az-appservice-dotnet/services/v1/ProcessorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
1 change: 0 additions & 1 deletion az-appservice-dotnet/services/v1/ProducerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit a464711

Please sign in to comment.