Initial commit

This commit is contained in:
2026-07-03 16:31:37 +07:00
commit 899c7c637d
1939 changed files with 641750 additions and 0 deletions

View File

@@ -0,0 +1,159 @@
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);
}
}