92 lines
3.6 KiB
C#
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");
|
|
}
|
|
}
|