📄 MatDenDagen/Infrastructure/Storage/BlobStorage/BlobStorageService.cs
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace MatDenDagen.Infrastructure.Storage.BlobStorage;

public sealed partial class BlobStorageService(
    ILogger<BlobStorageService> logger,
    IOptions<BlobStorageOptions> options,
    TimeProvider timeProvider
)
{
    private readonly string blobDirectory = options.Value.BlobDirectory;

    public async Task<SaveBlobResult> SaveBlob(Stream blob, CancellationToken cancellationToken)
    {
        var id = Guid.CreateVersion7(timeProvider.GetUtcNow()).ToString();
        LogCreatingFile(id);
        await using var fileStream = new FileStream(
            Path.Join(blobDirectory, id),
            FileMode.CreateNew,
            FileAccess.Write,
            FileShare.None,
            bufferSize: 4096,
            FileOptions.Asynchronous
        );
        await blob.CopyToAsync(fileStream, cancellationToken);
        return new(id);
    }

    public Stream? GetBlob(string id)
    {
        LogOpeningFile(id);

        var path = Path.Join(blobDirectory, id);
        return File.Exists(path)
            ? new FileStream(
                path,
                FileMode.Open,
                FileAccess.Read,
                FileShare.Read,
                bufferSize: 4096,
                FileOptions.Asynchronous
            )
            : null;
    }

    [LoggerMessage(LogLevel.Debug, "Creating new file {Id}.")]
    private partial void LogCreatingFile(string id);

    [LoggerMessage(LogLevel.Debug, "Opening file {Id}.")]
    private partial void LogOpeningFile(string id);
}

public sealed record SaveBlobResult(string Id);