160 lines
5.9 KiB
C#
160 lines
5.9 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using RobotNet10.StorageManager;
|
|
using SixLabors.ImageSharp;
|
|
|
|
namespace RobotNet10.FleetManager.Services;
|
|
|
|
/// <summary>
|
|
/// Service implementation for robot model image storage using StorageManager
|
|
/// Stores images with naming: {robotModelId}.png
|
|
/// </summary>
|
|
public class RobotModelImageStorageService : IRobotModelImageStorageService, IDisposable
|
|
{
|
|
private readonly ILogger<RobotModelImageStorageService> _logger;
|
|
private readonly StorageManager.StorageManager _storageManager;
|
|
private const string ImagePath = "robotModelImages";
|
|
private const string ContentType = "image/png";
|
|
|
|
public RobotModelImageStorageService(IOptionsMonitor<StorageConfig> optionsSnapshot, ILogger<RobotModelImageStorageService> logger)
|
|
{
|
|
_logger = logger;
|
|
|
|
var config = optionsSnapshot.Get("RobotModelImages");
|
|
|
|
ArgumentNullException.ThrowIfNull(config);
|
|
|
|
_storageManager = new StorageManager.StorageManager(config);
|
|
|
|
if (_logger.IsEnabled(LogLevel.Information))
|
|
{
|
|
_logger.LogInformation("RobotModelImageStorageService initialized");
|
|
}
|
|
}
|
|
|
|
private static string GetObjectName(Guid robotModelId) => robotModelId.ToString();
|
|
|
|
public async Task SaveImageAsync(Guid robotModelId, Stream imageStream, CancellationToken cancellationToken = default)
|
|
{
|
|
var objectName = GetObjectName(robotModelId);
|
|
|
|
try
|
|
{
|
|
// Reset stream position if seekable
|
|
if (imageStream.CanSeek)
|
|
{
|
|
imageStream.Position = 0;
|
|
}
|
|
|
|
// Get stream size - handle cases where Length might not be available
|
|
long size = imageStream.Length;
|
|
|
|
// If size is 0 or stream doesn't support Length, copy to MemoryStream
|
|
if (size == 0 || !imageStream.CanSeek)
|
|
{
|
|
using var memoryStream = new MemoryStream();
|
|
await imageStream.CopyToAsync(memoryStream, cancellationToken);
|
|
size = memoryStream.Length;
|
|
memoryStream.Position = 0;
|
|
|
|
await _storageManager.UploadAsync(ImagePath, objectName, memoryStream, size, ContentType, cancellationToken);
|
|
}
|
|
else
|
|
{
|
|
// Stream has valid length and is seekable, use directly
|
|
await _storageManager.UploadAsync(ImagePath, objectName, imageStream, size, ContentType, cancellationToken);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Failed to save image for robot model {RobotModelId}", robotModelId);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<Stream?> GetImageAsync(Guid robotModelId, CancellationToken cancellationToken = default)
|
|
{
|
|
var objectName = GetObjectName(robotModelId);
|
|
|
|
try
|
|
{
|
|
var exists = await _storageManager.ExistsAsync(ImagePath, objectName, cancellationToken);
|
|
if (!exists)
|
|
{
|
|
if (_logger.IsEnabled(LogLevel.Warning)) _logger.LogWarning("Image not found for robot model {RobotModelId}", robotModelId);
|
|
return null;
|
|
}
|
|
|
|
var stream = await _storageManager.GetFileAsync(ImagePath, objectName, cancellationToken);
|
|
return stream;
|
|
}
|
|
catch (FileNotFoundException)
|
|
{
|
|
if (_logger.IsEnabled(LogLevel.Warning)) _logger.LogWarning("Image not found for robot model {RobotModelId}", robotModelId);
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Failed to read image for robot model {RobotModelId}", robotModelId);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> DeleteImageAsync(Guid robotModelId, CancellationToken cancellationToken = default)
|
|
{
|
|
var objectName = GetObjectName(robotModelId);
|
|
|
|
try
|
|
{
|
|
var exists = await _storageManager.ExistsAsync(ImagePath, objectName, cancellationToken);
|
|
if (!exists)
|
|
{
|
|
if (_logger.IsEnabled(LogLevel.Warning)) _logger.LogWarning("Image not found for deletion: {RobotModelId}", robotModelId);
|
|
return false;
|
|
}
|
|
|
|
await _storageManager.DeleteAsync(ImagePath, objectName, cancellationToken);
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Failed to delete image for robot model {RobotModelId}", robotModelId);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> ImageExistsAsync(Guid robotModelId, CancellationToken cancellationToken = default)
|
|
{
|
|
var objectName = GetObjectName(robotModelId);
|
|
return await _storageManager.ExistsAsync(ImagePath, objectName, cancellationToken);
|
|
}
|
|
|
|
public async Task<(int width, int height)> GetImageDimensionsAsync(Stream imageStream, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
// Reset stream position if seekable
|
|
if (imageStream.CanSeek)
|
|
{
|
|
imageStream.Position = 0;
|
|
}
|
|
|
|
// Load image to get dimensions
|
|
using var image = await Image.LoadAsync(imageStream, cancellationToken);
|
|
|
|
return (image.Width, image.Height);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to extract image dimensions");
|
|
throw new InvalidOperationException("Invalid image format or corrupted file", ex);
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_storageManager?.Dispose();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|