Skip to content

Commit

Permalink
♻️ Tweaks to the image storage service and plug the service in.
Browse files Browse the repository at this point in the history
  • Loading branch information
hexawyz committed Jan 18, 2025
1 parent 034266b commit 9a2ec9c
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Exo.Service;

internal sealed class ImagesService
internal sealed class ImageStorageService
{
[TypeId(0x1D185C1A, 0x4903, 0x4D4A, 0x91, 0x20, 0x69, 0x4A, 0xE5, 0x2C, 0x07, 0x7A)]
private readonly struct ImageMetadata(UInt128 id, ushort width, ushort height, ImageFormat format, bool isAnimated)
Expand All @@ -19,7 +19,7 @@ private readonly struct ImageMetadata(UInt128 id, ushort width, ushort height, I
public bool IsAnimated { get; } = isAnimated;
}

public async Task<ImagesService> CreateAsync(IConfigurationContainer<string> imagesConfigurationContainer, string imageCacheDirectory, CancellationToken cancellationToken)
public static async Task<ImageStorageService> CreateAsync(IConfigurationContainer<string> imagesConfigurationContainer, string imageCacheDirectory, CancellationToken cancellationToken)
{
if (!Path.IsPathRooted(imageCacheDirectory)) throw new ArgumentException("Images directory path must be rooted.");

Expand Down Expand Up @@ -53,7 +53,7 @@ public async Task<ImagesService> CreateAsync(IConfigurationContainer<string> ima
private ChannelWriter<ImageChangeNotification>[]? _changeListeners;
private readonly AsyncLock _lock;

private ImagesService(IConfigurationContainer<string> imagesConfigurationContainer, string imageCacheDirectory, Dictionary<string, ImageMetadata> imageCollection)
private ImageStorageService(IConfigurationContainer<string> imagesConfigurationContainer, string imageCacheDirectory, Dictionary<string, ImageMetadata> imageCollection)
{
_imagesConfigurationContainer = imagesConfigurationContainer;
_imageCacheDirectory = imageCacheDirectory;
Expand Down Expand Up @@ -98,14 +98,14 @@ public async IAsyncEnumerable<ImageChangeNotification> WatchChangesAsync([Enumer
}
}

public async ValueTask AddImageAsync(string imageName, byte[] data, CancellationToken cancellationToken)
public async ValueTask AddImageAsync(string imageName, Memory<byte> data, CancellationToken cancellationToken)
{
if (!ImageNameSerializer.IsNameValid(imageName)) throw new ArgumentException("Invalid name.");
using (await _lock.WaitAsync(cancellationToken).ConfigureAwait(false))
{
if (_imageCollection.ContainsKey(imageName)) throw new ArgumentException("Name already in use.");

var info = SixLabors.ImageSharp.Image.Identify(data);
var info = SixLabors.ImageSharp.Image.Identify(data.Span);

ImageFormat imageFormat;
bool isAnimated = false;
Expand Down Expand Up @@ -139,7 +139,7 @@ public async ValueTask AddImageAsync(string imageName, byte[] data, Cancellation

var metadata = new ImageMetadata
(
XxHash128.HashToUInt128(data, unchecked((long)0x90AB71E534FD62C8U)),
XxHash128.HashToUInt128(data.Span, unchecked((long)0x90AB71E534FD62C8U)),
checked((ushort)info.Width),
checked((ushort)info.Height),
imageFormat,
Expand Down Expand Up @@ -170,6 +170,11 @@ public async ValueTask RemoveImageAsync(string imageName, CancellationToken canc
string fileName = GetFileName(metadata.Id);
File.Delete(fileName);
_imageCollection.Remove(imageName);

if (Volatile.Read(ref _changeListeners) is { } changeListeners)
{
changeListeners.TryWrite(new(WatchNotificationKind.Removal, new(metadata.Id, imageName, fileName, metadata.Width, metadata.Height, metadata.Format, metadata.IsAnimated)));
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

namespace Exo.Service.Grpc;

internal sealed class GrpcImagesService : IImagesService
internal sealed class GrpcImageService : IImageService
{
private readonly ImagesService _imagesService;
private readonly ImageStorageService _imageStorageService;

public GrpcImagesService(ImagesService imagesService) => _imagesService = imagesService;
public GrpcImageService(ImageStorageService imagesService) => _imageStorageService = imagesService;

public async IAsyncEnumerable<WatchNotification<Contracts.Ui.Settings.ImageInformation>> WatchImagesAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var notification in _imagesService.WatchChangesAsync(cancellationToken).ConfigureAwait(false))
await foreach (var notification in _imageStorageService.WatchChangesAsync(cancellationToken).ConfigureAwait(false))
{
yield return new()
{
Expand All @@ -23,11 +23,11 @@ internal sealed class GrpcImagesService : IImagesService

public async ValueTask AddImageAsync(ImageRegistrationRequest request, CancellationToken cancellationToken)
{
await _imagesService.AddImageAsync(request.ImageName, request.Data, cancellationToken).ConfigureAwait(false);
await _imageStorageService.AddImageAsync(request.ImageName, request.Data, cancellationToken).ConfigureAwait(false);
}

public async ValueTask RemoveImageAsync(ImageReference request, CancellationToken cancellationToken)
{
await _imagesService.RemoveImageAsync(request.ImageName, cancellationToken).ConfigureAwait(false);
await _imageStorageService.RemoveImageAsync(request.ImageName, cancellationToken).ConfigureAwait(false);
}
}
16 changes: 15 additions & 1 deletion src/Exo/Service/Exo.Service/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public Startup(IHostEnvironment environment, IConfiguration configuration)
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
string baseDirectory = Path.GetDirectoryName(typeof(Startup).Assembly.Location!)!;

services.AddRazorPages();
services.AddSingleton
(
Expand All @@ -42,7 +44,7 @@ public void ConfigureServices(IServiceCollection services)
(
sp =>
{
var configurationService = new ConfigurationService(Path.Combine(Path.GetDirectoryName(typeof(Startup).Assembly.Location!)!, "cfg"));
var configurationService = new ConfigurationService(Path.Combine(baseDirectory, "cfg"));
ConfigurationMigrationService.InitializeAsync(configurationService, Program.GitCommitId, default).GetAwaiter().GetResult();
return configurationService;
}
Expand All @@ -54,6 +56,7 @@ public void ConfigureServices(IServiceCollection services)
services.AddKeyedSingleton(ConfigurationContainerNames.Assembly, (sp, name) => sp.GetRequiredService<ConfigurationService>().GetContainer((string)name, AssemblyNameSerializer.Instance));
services.AddKeyedSingleton(ConfigurationContainerNames.CustomMenu, (sp, name) => sp.GetRequiredService<ConfigurationService>().GetContainer((string)name));
services.AddKeyedSingleton(ConfigurationContainerNames.Lighting, (sp, name) => sp.GetRequiredService<ConfigurationService>().GetContainer((string)name));
services.AddKeyedSingleton(ConfigurationContainerNames.Images, (sp, name) => sp.GetRequiredService<ConfigurationService>().GetContainer((string)name, ImageNameSerializer.Instance));
if (Environment.IsDevelopment())
{
services.AddSingleton<IAssemblyDiscovery, DebugAssemblyDiscovery>();
Expand Down Expand Up @@ -163,6 +166,15 @@ public void ConfigureServices(IServiceCollection services)
);
services.AddSingleton<ImageService>();
services.AddSingleton
(
sp => ImageStorageService.CreateAsync
(
sp.GetRequiredKeyedService<IConfigurationContainer<string>>(ConfigurationContainerNames.Images),
Path.Combine(baseDirectory, "img"),
default
).GetAwaiter().GetResult()
);
services.AddSingleton
(
sp => CustomMenuService.CreateAsync
(
Expand Down Expand Up @@ -239,6 +251,7 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton<GrpcDeviceService>();
services.AddSingleton<GrpcPowerService>();
services.AddSingleton<GrpcLightingService>();
services.AddSingleton<GrpcImageService>();
services.AddSingleton<GrpcSensorService>();
services.AddSingleton<GrpcCoolingService>();
services.AddSingleton<GrpcMouseService>();
Expand Down Expand Up @@ -281,6 +294,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
endpoints.MapGrpcService<GrpcDeviceService>().AddEndpointFilter(settingsEndpointFilter);
endpoints.MapGrpcService<GrpcPowerService>().AddEndpointFilter(settingsEndpointFilter);
endpoints.MapGrpcService<GrpcLightingService>().AddEndpointFilter(settingsEndpointFilter);
endpoints.MapGrpcService<GrpcImageService>().AddEndpointFilter(settingsEndpointFilter);
endpoints.MapGrpcService<GrpcSensorService>().AddEndpointFilter(settingsEndpointFilter);
endpoints.MapGrpcService<GrpcCoolingService>().AddEndpointFilter(settingsEndpointFilter);
endpoints.MapGrpcService<GrpcMouseService>().AddEndpointFilter(settingsEndpointFilter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Exo.Contracts.Ui.Settings;
/// <summary>This service manages the internal image collection.</summary>
/// <remarks>The image collection is a persisted collection of images to be used within the service.</remarks>
[ServiceContract(Name = "Images")]
public interface IImagesService
public interface IImageService
{
/// <summary>Watches information on images available in the service.</summary>
/// <remarks>Images available are not necessarily loaded in memory.</remarks>
Expand Down

0 comments on commit 9a2ec9c

Please sign in to comment.