stitchaton/src/Oh.My.Stitcher/Program.cs

107 lines
3.5 KiB
C#
Raw Normal View History

2025-07-27 20:19:12 +07:00
using System.Buffers;
2025-07-27 16:02:56 +07:00
using System.IO.Pipelines;
using Microsoft.AspNetCore.Http.Json;
2025-07-27 23:22:07 +07:00
using Microsoft.Extensions.Caching.Memory;
2025-07-27 16:02:56 +07:00
using NetVips;
using Oh.My.Stitcher;
using Validation;
using ZLogger;
WebApplicationBuilder builder = WebApplication.CreateSlimBuilder(args);
builder.Logging.ClearProviders().AddZLoggerConsole();
2025-07-27 23:22:07 +07:00
builder.Services.AddMemoryCache();
2025-07-27 16:02:56 +07:00
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.TypeInfoResolver = StitchSerializerContext.Default;
});
WebApplication app = builder.Build();
ILoggerFactory loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
ILogger logger = loggerFactory.CreateLogger<Program>();
string? tilesDirectory = Environment.GetEnvironmentVariable("ASSET_PATH_RO");
2025-07-27 23:22:07 +07:00
string cacheDirectory = Path.Combine(Path.GetTempPath(), "oh-my-stitch");
Directory.CreateDirectory(cacheDirectory);
2025-07-27 16:02:56 +07:00
// sanity check
Assumes.NotNullOrEmpty(tilesDirectory);
Assumes.True(File.Exists(Path.Combine(tilesDirectory, "A1.png")));
Assumes.True(File.Exists(Path.Combine(tilesDirectory, "AE55.png")));
2025-07-27 23:22:07 +07:00
2025-07-27 16:02:56 +07:00
app.UseDefaultFiles();
app.UseStaticFiles();
2025-07-27 23:22:07 +07:00
app.MapPost("/api/image/generate", (Stitch request, IMemoryCache cache) =>
2025-07-27 16:02:56 +07:00
{
Pipe pipe = new();
_ = Task.Run(async () =>
{
2025-07-27 23:22:07 +07:00
Image? image = null;
2025-07-27 16:02:56 +07:00
List<Image> images = [];
try
{
2025-07-27 23:22:07 +07:00
bool created = Tile.TryCreate(in request, tilesDirectory, images, logger, cache, out image,
out string? cacheKey, out string? cacheFile);
if( !created && cacheFile != null )
{
logger.ZLogDebug($"cache hit key: {cacheKey}, file: {cacheFile}");
await using FileStream cacheStream = new(cacheFile, FileMode.Open, FileAccess.Read, FileShare.Read);
await cacheStream.CopyToAsync(pipe.Writer.AsStream());
return;
}
if( cacheKey == null )
{
image?.WriteToStream(pipe.Writer.AsStream(), ".png");
return;
}
2025-07-27 20:19:12 +07:00
Pipe innerPipe = new();
_ = Task.Run(async () =>
{
2025-07-27 23:22:07 +07:00
string newCacheFile = Path.Combine(cacheDirectory, $"{cacheKey}.png");
MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(10))
.RegisterPostEvictionCallback((_, value, _, _) =>
{
if( value is string path )
File.Delete(path);
});
logger.ZLogDebug($"save cache key: {cacheKey}, file: {newCacheFile}");
cache.Set(cacheKey!, newCacheFile, cacheEntryOptions);
await using FileStream cacheStream = new(newCacheFile, FileMode.Create, FileAccess.Write, FileShare.Read);
2025-07-27 20:19:12 +07:00
while( true )
{
ReadResult result = await innerPipe.Reader.ReadAsync();
ReadOnlySequence<byte> buffer = result.Buffer;
2025-07-27 23:22:07 +07:00
foreach( ReadOnlyMemory<byte> segment in buffer )
await Task.WhenAll(pipe.Writer.WriteAsync(segment).AsTask(), cacheStream.WriteAsync(segment).AsTask());
innerPipe.Reader.AdvanceTo(buffer.End);
if( result.IsCompleted )
break;
2025-07-27 20:19:12 +07:00
}
});
2025-07-27 23:22:07 +07:00
image?.WriteToStream(innerPipe.Writer.AsStream(), ".png");
2025-07-27 16:02:56 +07:00
}
catch( Exception e )
{
logger.ZLogError(e, $"Error when generating image");
using Image errorImage = Tile.CreateError(e);
errorImage.WriteToStream(pipe.Writer.AsStream(), ".png");
}
finally
{
2025-07-27 23:22:07 +07:00
image?.Dispose();
2025-07-27 16:02:56 +07:00
foreach( Image img in images )
img.Dispose();
await pipe.Writer.CompleteAsync();
}
});
return Results.Stream(pipe.Reader.AsStream(), "image/png");
});
app.Run();