From f2a9af1c48980df020d7fc53cc4b5f4b0ac6a9eb Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 16 Jul 2018 21:51:59 +0200 Subject: [PATCH] Add all files to commit. --- .../Assets/AssetCommandMiddleware.cs | 23 +++++- .../Assets/AssetCreatedResult.cs | 28 ++++++++ .../Assets/AssetGrain.cs | 24 ++++--- .../Assets/Commands/CreateAsset.cs | 3 + .../Assets/FileTypeTagGenerator.cs | 27 +++++++ .../Assets/ImageTagGenerator.cs | 39 ++++++++++ .../Tags/ITagGenerator.cs | 16 +++++ .../Assets/AssetCreated.cs | 3 + src/Squidex.Infrastructure/FileExtensions.cs | 16 ++++- .../Assets/AssetCommandMiddlewareTests.cs | 23 +++++- .../Assets/AssetGrainTests.cs | 24 ++++++- .../Assets/FileTypeTagGeneratorTests.cs | 56 +++++++++++++++ .../Assets/ImageTagGeneratorTests.cs | 72 +++++++++++++++++++ .../FileExtensionsTests.cs | 2 + 14 files changed, 338 insertions(+), 18 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Assets/FileTypeTagGenerator.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Assets/ImageTagGenerator.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Tags/ITagGenerator.cs create mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTypeTagGeneratorTests.cs create mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageTagGeneratorTests.cs diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs index bb349d793..2688ef256 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs @@ -6,9 +6,11 @@ // ========================================================================== using System; +using System.Collections.Generic; 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; @@ -19,18 +21,23 @@ namespace Squidex.Domain.Apps.Entities.Assets { private readonly IAssetStore assetStore; private readonly IAssetThumbnailGenerator assetThumbnailGenerator; + private readonly IEnumerable> tagGenerators; public AssetCommandMiddleware( IGrainFactory grainFactory, IAssetStore assetStore, - IAssetThumbnailGenerator assetThumbnailGenerator) + IAssetThumbnailGenerator assetThumbnailGenerator, + IEnumerable> tagGenerators) : base(grainFactory) { Guard.NotNull(assetStore, nameof(assetStore)); Guard.NotNull(assetThumbnailGenerator, nameof(assetThumbnailGenerator)); + Guard.NotNull(tagGenerators, nameof(tagGenerators)); this.assetStore = assetStore; this.assetThumbnailGenerator = assetThumbnailGenerator; + + this.tagGenerators = tagGenerators; } public async override Task HandleAsync(CommandContext context, Func next) @@ -39,14 +46,26 @@ namespace Squidex.Domain.Apps.Entities.Assets { case CreateAsset createAsset: { + if (createAsset.Tags == null) + { + createAsset.Tags = new HashSet(); + } + createAsset.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(createAsset.File.OpenRead()); + foreach (var tagGenerator in tagGenerators) + { + tagGenerator.GenerateTags(createAsset, createAsset.Tags); + } + + var originalTags = new HashSet(createAsset.Tags); + await assetStore.UploadAsync(context.ContextId.ToString(), createAsset.File.OpenRead()); try { var result = await ExecuteCommandAsync(createAsset) as AssetSavedResult; - context.Complete(EntityCreatedResult.Create(createAsset.AssetId, result.Version)); + context.Complete(new AssetCreatedResult(createAsset.AssetId, originalTags, result.Version)); await assetStore.CopyAsync(context.ContextId.ToString(), createAsset.AssetId.ToString(), result.FileVersion, null); } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs new file mode 100644 index 000000000..8abb01c95 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs @@ -0,0 +1,28 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Squidex.Infrastructure.Commands; + +namespace Squidex.Domain.Apps.Entities.Assets +{ + public sealed class AssetCreatedResult : EntitySavedResult + { + public Guid Id { get; } + + public HashSet Tags { get; } + + public AssetCreatedResult(Guid id, HashSet tags, long version) + : base(version) + { + Id = id; + + Tags = tags; + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs index fac3ca651..c238593a9 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs @@ -42,10 +42,12 @@ namespace Squidex.Domain.Apps.Entities.Assets switch (command) { case CreateAsset createRule: - return CreateReturnAsync(createRule, c => + return CreateReturnAsync(createRule, async c => { GuardAsset.CanCreate(c); + c.Tags = await tagService.NormalizeTagsAsync(c.AppId.Id, TagGroups.Assets, c.Tags, Snapshot.Tags); + Create(c); return new AssetSavedResult(Version, Snapshot.FileVersion); @@ -59,12 +61,14 @@ namespace Squidex.Domain.Apps.Entities.Assets return new AssetSavedResult(Version, Snapshot.FileVersion); }); - case RenameAsset renameAsset: - return UpdateAsync(renameAsset, c => + case TagAsset tagAsset: + return UpdateAsync(tagAsset, async c => { - GuardAsset.CanRename(c, Snapshot.FileName); + GuardAsset.CanTag(c); - Rename(c); + c.Tags = await tagService.NormalizeTagsAsync(Snapshot.AppId.Id, TagGroups.Assets, c.Tags, Snapshot.Tags); + + Tag(c); }); case DeleteAsset deleteAsset: return UpdateAsync(deleteAsset, async c => @@ -75,14 +79,12 @@ namespace Squidex.Domain.Apps.Entities.Assets Delete(c); }); - case TagAsset tagAsset: - return UpdateAsync(tagAsset, async c => + case RenameAsset renameAsset: + return UpdateAsync(renameAsset, c => { - GuardAsset.CanTag(c); - - c.Tags = await tagService.NormalizeTagsAsync(Snapshot.AppId.Id, TagGroups.Assets, c.Tags, Snapshot.Tags); + GuardAsset.CanRename(c, Snapshot.FileName); - Tag(c); + Rename(c); }); default: throw new NotSupportedException(); diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs index f421d9ed8..829cf0ce5 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; @@ -19,6 +20,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.Commands public ImageInfo ImageInfo { get; set; } + public HashSet Tags { get; set; } + public CreateAsset() { AssetId = Guid.NewGuid(); diff --git a/src/Squidex.Domain.Apps.Entities/Assets/FileTypeTagGenerator.cs b/src/Squidex.Domain.Apps.Entities/Assets/FileTypeTagGenerator.cs new file mode 100644 index 000000000..88df12a99 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Assets/FileTypeTagGenerator.cs @@ -0,0 +1,27 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using Squidex.Domain.Apps.Entities.Assets.Commands; +using Squidex.Domain.Apps.Entities.Tags; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Entities.Assets +{ + public sealed class FileTypeTagGenerator : ITagGenerator + { + public void GenerateTags(CreateAsset source, HashSet tags) + { + var extension = source.File?.FileName?.FileType(); + + if (!string.IsNullOrWhiteSpace(extension)) + { + tags.Add($"type/{extension.ToLowerInvariant()}"); + } + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Assets/ImageTagGenerator.cs b/src/Squidex.Domain.Apps.Entities/Assets/ImageTagGenerator.cs new file mode 100644 index 000000000..c44b7073a --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Assets/ImageTagGenerator.cs @@ -0,0 +1,39 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using Squidex.Domain.Apps.Entities.Assets.Commands; +using Squidex.Domain.Apps.Entities.Tags; + +namespace Squidex.Domain.Apps.Entities.Assets +{ + public sealed class ImageTagGenerator : ITagGenerator + { + public void GenerateTags(CreateAsset source, HashSet tags) + { + if (source.ImageInfo != null) + { + tags.Add("image"); + + var wh = source.ImageInfo.PixelWidth + source.ImageInfo.PixelHeight; + + if (wh > 2000) + { + tags.Add("image/large"); + } + else if (wh > 1000) + { + tags.Add("image/medium"); + } + else + { + tags.Add("image/small"); + } + } + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Tags/ITagGenerator.cs b/src/Squidex.Domain.Apps.Entities/Tags/ITagGenerator.cs new file mode 100644 index 000000000..eac2b6f0a --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Tags/ITagGenerator.cs @@ -0,0 +1,16 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; + +namespace Squidex.Domain.Apps.Entities.Tags +{ + public interface ITagGenerator + { + void GenerateTags(T source, HashSet tags); + } +} diff --git a/src/Squidex.Domain.Apps.Events/Assets/AssetCreated.cs b/src/Squidex.Domain.Apps.Events/Assets/AssetCreated.cs index 53fea1234..2266f7f2e 100644 --- a/src/Squidex.Domain.Apps.Events/Assets/AssetCreated.cs +++ b/src/Squidex.Domain.Apps.Events/Assets/AssetCreated.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Events.Assets @@ -25,5 +26,7 @@ namespace Squidex.Domain.Apps.Events.Assets public int? PixelWidth { get; set; } public int? PixelHeight { get; set; } + + public HashSet Tags { get; set; } } } diff --git a/src/Squidex.Infrastructure/FileExtensions.cs b/src/Squidex.Infrastructure/FileExtensions.cs index 07f4cc994..bc54d13fb 100644 --- a/src/Squidex.Infrastructure/FileExtensions.cs +++ b/src/Squidex.Infrastructure/FileExtensions.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Globalization; using System.IO; @@ -22,13 +23,26 @@ namespace Squidex.Infrastructure "TB" }; + private static readonly Dictionary UnifiedExtensions = new Dictionary() + { + ["jpeg"] = "jpg" + }; + public static string FileType(this string fileName) { try { var fileInfo = new FileInfo(fileName); + var fileType = fileInfo.Extension.Substring(1).ToLowerInvariant(); - return fileInfo.Extension.Substring(1).ToLowerInvariant(); + if (UnifiedExtensions.TryGetValue(fileType, out var unified)) + { + return unified; + } + else + { + return fileType; + } } catch { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs index 098b0d3f1..9a3eca2ea 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -28,6 +29,7 @@ namespace Squidex.Domain.Apps.Entities.Assets private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake(); private readonly IAssetStore assetStore = A.Fake(); private readonly ITagService tagService = A.Fake(); + private readonly ITagGenerator tagGenerator = A.Fake>(); private readonly IGrainFactory grainFactory = A.Fake(); private readonly Guid assetId = Guid.NewGuid(); private readonly Stream stream = new MemoryStream(); @@ -48,23 +50,38 @@ namespace Squidex.Domain.Apps.Entities.Assets asset = new AssetGrain(Store, tagService, A.Dummy()); asset.OnActivateAsync(Id).Wait(); + A.CallTo(() => tagService.NormalizeTagsAsync(AppId, TagGroups.Assets, A>.Ignored, A>.Ignored)) + .Returns(new HashSet()); + A.CallTo(() => grainFactory.GetGrain(Id, null)) .Returns(asset); - sut = new AssetCommandMiddleware(grainFactory, assetStore, assetThumbnailGenerator); + sut = new AssetCommandMiddleware(grainFactory, assetStore, assetThumbnailGenerator, new[] { tagGenerator }); } [Fact] public async Task Create_should_create_domain_object() { - var context = CreateContextForCommand(new CreateAsset { AssetId = assetId, File = file }); + var command = new CreateAsset { AssetId = assetId, File = file }; + var context = CreateContextForCommand(command); + + A.CallTo(() => tagGenerator.GenerateTags(command, A>.Ignored)) + .Invokes(new Action>((c, tags) => + { + tags.Add("tag1"); + tags.Add("tag2"); + })); SetupStore(0, context.ContextId); SetupImageInfo(); await sut.HandleAsync(context); - Assert.Equal(assetId, context.Result>().IdOrValue); + var result = context.Result(); + + Assert.Equal(assetId, result.Id); + Assert.Contains("tag1", result.Tags); + Assert.Contains("tag2", result.Tags); AssertAssetHasBeenUploaded(0, context.ContextId); AssertAssetImageChecked(); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetGrainTests.cs index 069c8a8dc..ae2961ca2 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetGrainTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetGrainTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using FakeItEasy; @@ -37,6 +38,9 @@ namespace Squidex.Domain.Apps.Entities.Assets public AssetGrainTests() { + A.CallTo(() => tagService.NormalizeTagsAsync(AppId, TagGroups.Assets, A>.Ignored, A>.Ignored)) + .Returns(new HashSet()); + sut = new AssetGrain(Store, tagService, A.Dummy()); sut.OnActivateAsync(Id).Wait(); } @@ -71,7 +75,8 @@ namespace Squidex.Domain.Apps.Entities.Assets FileVersion = 0, MimeType = file.MimeType, PixelWidth = image.PixelWidth, - PixelHeight = image.PixelHeight + PixelHeight = image.PixelHeight, + Tags = new HashSet() }) ); } @@ -122,6 +127,23 @@ namespace Squidex.Domain.Apps.Entities.Assets ); } + [Fact] + public async Task Tag_should_create_events() + { + var command = new TagAsset(); + + await ExecuteCreateAsync(); + + var result = await sut.ExecuteAsync(CreateAssetCommand(command)); + + result.ShouldBeEquivalent(new EntitySavedResult(1)); + + LastEvents + .ShouldHaveSameEvents( + CreateAssetEvent(new AssetTagged { Tags = new HashSet() }) + ); + } + [Fact] public async Task Delete_should_create_events_with_total_file_size() { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTypeTagGeneratorTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTypeTagGeneratorTests.cs new file mode 100644 index 000000000..272ba9c8c --- /dev/null +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTypeTagGeneratorTests.cs @@ -0,0 +1,56 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using Squidex.Domain.Apps.Entities.Assets.Commands; +using Squidex.Infrastructure.Assets; +using Xunit; + +namespace Squidex.Domain.Apps.Entities.Assets +{ + public class FileTypeTagGeneratorTests + { + private readonly HashSet tags = new HashSet(); + private readonly FileTypeTagGenerator sut = new FileTypeTagGenerator(); + + [Fact] + public void Should_not_add_tag_if_no_file_info() + { + var command = new CreateAsset(); + + sut.GenerateTags(command, tags); + + Assert.Empty(tags); + } + + [Fact] + public void Should_add_file_type() + { + var command = new CreateAsset + { + File = new AssetFile("File.DOCX", "Mime", 100, () => null) + }; + + sut.GenerateTags(command, tags); + + Assert.Contains("type/docx", tags); + } + + [Fact] + public void Should_add_blob_if_without_extension() + { + var command = new CreateAsset + { + File = new AssetFile("File", "Mime", 100, () => null) + }; + + sut.GenerateTags(command, tags); + + Assert.Contains("type/blob", tags); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageTagGeneratorTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageTagGeneratorTests.cs new file mode 100644 index 000000000..2600bcbbb --- /dev/null +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageTagGeneratorTests.cs @@ -0,0 +1,72 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using Squidex.Domain.Apps.Entities.Assets.Commands; +using Squidex.Infrastructure.Assets; +using Xunit; + +namespace Squidex.Domain.Apps.Entities.Assets +{ + public class ImageTagGeneratorTests + { + private readonly HashSet tags = new HashSet(); + private readonly ImageTagGenerator sut = new ImageTagGenerator(); + + [Fact] + public void Should_not_add_tag_if_no_image() + { + var command = new CreateAsset(); + + sut.GenerateTags(command, tags); + + Assert.Empty(tags); + } + + [Fact] + public void Should_add_image_tag_if_small() + { + var command = new CreateAsset + { + ImageInfo = new ImageInfo(100, 100) + }; + + sut.GenerateTags(command, tags); + + Assert.Contains("image", tags); + Assert.Contains("image/small", tags); + } + + [Fact] + public void Should_add_image_tag_if_medium() + { + var command = new CreateAsset + { + ImageInfo = new ImageInfo(800, 600) + }; + + sut.GenerateTags(command, tags); + + Assert.Contains("image", tags); + Assert.Contains("image/medium", tags); + } + + [Fact] + public void Should_add_image_tag_if_large() + { + var command = new CreateAsset + { + ImageInfo = new ImageInfo(1200, 1400) + }; + + sut.GenerateTags(command, tags); + + Assert.Contains("image", tags); + Assert.Contains("image/large", tags); + } + } +} diff --git a/tests/Squidex.Infrastructure.Tests/FileExtensionsTests.cs b/tests/Squidex.Infrastructure.Tests/FileExtensionsTests.cs index 6912e0ba0..0c7ece1b6 100644 --- a/tests/Squidex.Infrastructure.Tests/FileExtensionsTests.cs +++ b/tests/Squidex.Infrastructure.Tests/FileExtensionsTests.cs @@ -16,6 +16,8 @@ namespace Squidex.Infrastructure [InlineData("test.MP4", "mp4")] [InlineData("test.txt", "txt")] [InlineData("test.TXT", "txt")] + [InlineData("test.jpg", "jpg")] + [InlineData("test.jpeg", "jpg")] public void Should_calculate_file_type(string fileName, string expected) { var actual = fileName.FileType();