PlotDirector/PlotLine/Services/FloorPlanBackgroundImageService.cs

92 lines
3.6 KiB
C#

using PlotLine.Models;
using SkiaSharp;
namespace PlotLine.Services;
public interface IFloorPlanBackgroundImageService
{
Task<FloorPlanBackgroundImageUploadResult> UploadAsync(FloorPlan floorPlan, FloorPlanFloor floor, IFormFile upload);
void DeleteBackgroundFile(string? backgroundImagePath);
}
public sealed record FloorPlanBackgroundImageUploadResult(
bool Succeeded,
string? StoragePath = null,
string? OriginalFileName = null,
string? ErrorMessage = null);
public sealed class FloorPlanBackgroundImageService(
IUploadStorageService uploadStorage,
ILogger<FloorPlanBackgroundImageService> logger) : IFloorPlanBackgroundImageService
{
private const long MaxUploadBytes = 10L * 1024L * 1024L;
private static readonly HashSet<string> SupportedExtensions = new(StringComparer.OrdinalIgnoreCase) { ".jpg", ".jpeg", ".png", ".webp" };
public async Task<FloorPlanBackgroundImageUploadResult> UploadAsync(FloorPlan floorPlan, FloorPlanFloor floor, IFormFile upload)
{
if (upload.Length <= 0)
{
return new(false, ErrorMessage: "Choose a background image to upload.");
}
if (upload.Length > MaxUploadBytes)
{
return new(false, ErrorMessage: "Background image must be 10 MB or smaller.");
}
var extension = Path.GetExtension(upload.FileName);
if (!SupportedExtensions.Contains(extension))
{
return new(false, ErrorMessage: "Background image must be a PNG, JPG, JPEG or WebP file.");
}
var originalFileName = Path.GetFileName(upload.FileName);
if (string.IsNullOrWhiteSpace(originalFileName))
{
originalFileName = $"background{extension}";
}
byte[] bytes;
await using (var stream = upload.OpenReadStream())
using (var memory = new MemoryStream())
{
await stream.CopyToAsync(memory);
bytes = memory.ToArray();
}
try
{
using var codec = SKCodec.Create(new MemoryStream(bytes)) ?? throw new InvalidDataException();
if (codec.EncodedFormat is not (SKEncodedImageFormat.Jpeg or SKEncodedImageFormat.Png or SKEncodedImageFormat.Webp))
{
return new(false, ErrorMessage: "Background image must be a valid PNG, JPG, JPEG or WebP file.");
}
}
catch (Exception ex) when (ex is InvalidDataException or ArgumentException)
{
return new(false, ErrorMessage: "Background image must be a valid PNG, JPG, JPEG or WebP file.");
}
var storedName = $"background-{Guid.NewGuid():N}{extension.ToLowerInvariant()}";
var uploadRoot = uploadStorage.GetDirectory("floor-plans", floorPlan.ProjectID.ToString(), floorPlan.FloorPlanID.ToString(), "backgrounds");
var absolutePath = Path.Combine(uploadRoot, storedName);
var storagePath = uploadStorage.GetPublicPath("floor-plans", floorPlan.ProjectID.ToString(), floorPlan.FloorPlanID.ToString(), "backgrounds", storedName);
try
{
await File.WriteAllBytesAsync(absolutePath, bytes);
return new(true, storagePath, originalFileName);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Floor plan background upload failed for floor {FloorPlanFloorID}.", floor.FloorPlanFloorID);
return new(false, ErrorMessage: "Background image could not be saved. Try another image.");
}
}
public void DeleteBackgroundFile(string? backgroundImagePath)
{
uploadStorage.TryDeleteFile(backgroundImagePath, "uploads/floor-plans");
}
}