// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Threading.Tasks; using Orleans; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Tags; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Assets { public sealed class AssetCommandMiddleware : GrainCommandMiddleware { private readonly IAssetFileStore assetFileStore; private readonly IAssetEnricher assetEnricher; private readonly IAssetQueryService assetQuery; private readonly IAssetThumbnailGenerator assetThumbnailGenerator; private readonly IContextProvider contextProvider; private readonly IEnumerable> tagGenerators; public AssetCommandMiddleware( IGrainFactory grainFactory, IAssetEnricher assetEnricher, IAssetQueryService assetQuery, IAssetFileStore assetFileStore, IAssetThumbnailGenerator assetThumbnailGenerator, IContextProvider contextProvider, IEnumerable> tagGenerators) : base(grainFactory) { Guard.NotNull(assetEnricher); Guard.NotNull(assetFileStore); Guard.NotNull(assetQuery); Guard.NotNull(assetThumbnailGenerator); Guard.NotNull(contextProvider); Guard.NotNull(tagGenerators); this.assetFileStore = assetFileStore; this.assetEnricher = assetEnricher; this.assetQuery = assetQuery; this.assetThumbnailGenerator = assetThumbnailGenerator; this.contextProvider = contextProvider; this.tagGenerators = tagGenerators; } public override async Task HandleAsync(CommandContext context, Func next) { var tempFile = context.ContextId.ToString(); switch (context.Command) { case CreateAsset createAsset: { await EnrichWithImageInfosAsync(createAsset); await EnrichWithHashAndUploadAsync(createAsset, tempFile); try { var ctx = contextProvider.Context.Clone().WithNoAssetEnrichment(); var existings = await assetQuery.QueryByHashAsync(ctx, createAsset.AppId.Id, createAsset.FileHash); foreach (var existing in existings) { if (IsDuplicate(existing, createAsset.File)) { var result = new AssetCreatedResult(existing, true); context.Complete(result); await next(); return; } } GenerateTags(createAsset); await HandleCoreAsync(context, next); var asset = context.Result(); context.Complete(new AssetCreatedResult(asset, false)); await assetFileStore.CopyAsync(tempFile, createAsset.AssetId, asset.FileVersion); } finally { await assetFileStore.DeleteAsync(tempFile); } break; } case UpdateAsset updateAsset: { await EnrichWithImageInfosAsync(updateAsset); await EnrichWithHashAndUploadAsync(updateAsset, tempFile); try { await HandleCoreAsync(context, next); var asset = context.Result(); await assetFileStore.CopyAsync(tempFile, updateAsset.AssetId, asset.FileVersion); } finally { await assetFileStore.DeleteAsync(tempFile); } break; } default: await HandleCoreAsync(context, next); break; } } private async Task HandleCoreAsync(CommandContext context, Func next) { await base.HandleAsync(context, next); if (context.PlainResult is IAssetEntity asset && !(context.PlainResult is IEnrichedAssetEntity)) { var enriched = await assetEnricher.EnrichAsync(asset, contextProvider.Context); context.Complete(enriched); } } private static bool IsDuplicate(IAssetEntity asset, AssetFile file) { return asset?.FileName == file.FileName && asset.FileSize == file.FileSize; } private async Task EnrichWithImageInfosAsync(UploadAssetCommand command) { command.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(command.File.OpenRead()); } private async Task EnrichWithHashAndUploadAsync(UploadAssetCommand command, string tempFile) { using (var hashStream = new HasherStream(command.File.OpenRead(), HashAlgorithmName.SHA256)) { await assetFileStore.UploadAsync(tempFile, hashStream); command.FileHash = $"{hashStream.GetHashStringAndReset()}{command.File.FileName}{command.File.FileSize}".Sha256Base64(); } } private void GenerateTags(CreateAsset createAsset) { if (createAsset.Tags == null) { createAsset.Tags = new HashSet(); } foreach (var tagGenerator in tagGenerators) { tagGenerator.GenerateTags(createAsset, createAsset.Tags); } } } }