mirror of https://github.com/Squidex/squidex.git
212 changed files with 3824 additions and 1790 deletions
@ -0,0 +1,110 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Linq; |
|||
using Squidex.Infrastructure.Json.Objects; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Assets |
|||
{ |
|||
public sealed class AssetMetadata : Dictionary<string, IJsonValue> |
|||
{ |
|||
private static readonly char[] PathSeparators = { '.', '[', ']' }; |
|||
|
|||
public AssetMetadata SetPixelWidth(int value) |
|||
{ |
|||
this["pixelWidth"] = JsonValue.Create(value); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public AssetMetadata SetPixelHeight(int value) |
|||
{ |
|||
this["pixelHeight"] = JsonValue.Create(value); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public int? GetPixelWidth() |
|||
{ |
|||
if (TryGetValue("pixelWidth", out var n) && n is JsonNumber number) |
|||
{ |
|||
return (int)number.Value; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public int? GetPixelHeight() |
|||
{ |
|||
if (TryGetValue("pixelHeight", out var n) && n is JsonNumber number) |
|||
{ |
|||
return (int)number.Value; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public bool TryGetNumber(string name, out double result) |
|||
{ |
|||
if (TryGetValue(name, out var v) && v is JsonNumber n) |
|||
{ |
|||
result = n.Value; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
result = 0; |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public bool TryGetString(string name, [MaybeNullWhen(false)] out string result) |
|||
{ |
|||
if (TryGetValue(name, out var v) && v is JsonString s) |
|||
{ |
|||
result = s.Value; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
result = null!; |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public bool TryGetByPath(string? path, [MaybeNullWhen(false)] out object result) |
|||
{ |
|||
return TryGetByPath(path?.Split(PathSeparators, StringSplitOptions.RemoveEmptyEntries), out result!); |
|||
} |
|||
|
|||
public bool TryGetByPath(IEnumerable<string>? path, [MaybeNullWhen(false)] out object result) |
|||
{ |
|||
result = this; |
|||
|
|||
if (path == null || !path.Any()) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
result = null!; |
|||
|
|||
if (!TryGetValue(path.First(), out var json)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
json.TryGetByPath(path.Skip(1), out var temp); |
|||
|
|||
result = temp!; |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
@ -1,14 +1,17 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities |
|||
namespace Squidex.Domain.Apps.Core.Assets |
|||
{ |
|||
public interface IUpdateableEntityWithVersion |
|||
public enum AssetType |
|||
{ |
|||
long Version { get; set; } |
|||
Unknown, |
|||
Image, |
|||
Audio, |
|||
Video |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using DeepEqual.Syntax; |
|||
|
|||
namespace Squidex.Domain.Apps.Core |
|||
{ |
|||
public sealed class DeepComparer<T> : IEqualityComparer<T> |
|||
{ |
|||
public static readonly DeepComparer<T> Instance = new DeepComparer<T>(); |
|||
|
|||
private DeepComparer() |
|||
{ |
|||
} |
|||
|
|||
public bool Equals(T x, T y) |
|||
{ |
|||
return x.IsDeepEqual(y); |
|||
} |
|||
|
|||
public int GetHashCode(T obj) |
|||
{ |
|||
return 0; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure.Queries; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors |
|||
{ |
|||
public sealed class FirstPascalPathConverter<TValue> : TransformVisitor<TValue> |
|||
{ |
|||
private static readonly FirstPascalPathConverter<TValue> Instance = new FirstPascalPathConverter<TValue>(); |
|||
|
|||
private FirstPascalPathConverter() |
|||
{ |
|||
} |
|||
|
|||
public static FilterNode<TValue>? Transform(FilterNode<TValue> node) |
|||
{ |
|||
return node.Accept(Instance); |
|||
} |
|||
|
|||
public override FilterNode<TValue>? Visit(CompareFilter<TValue> nodeIn) |
|||
{ |
|||
return new CompareFilter<TValue>(nodeIn.Path.ToFirstPascalCase(), nodeIn.Operator, nodeIn.Value); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Linq; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Queries; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors |
|||
{ |
|||
public static class FirstPascalPathExtension |
|||
{ |
|||
public static PropertyPath ToFirstPascalCase(this PropertyPath path) |
|||
{ |
|||
var result = path.ToList(); |
|||
|
|||
result[0] = result[0].ToPascalCase(); |
|||
|
|||
return new PropertyPath(result); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,222 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.Assets; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Assets; |
|||
using Squidex.Infrastructure.Json.Objects; |
|||
using Squidex.Infrastructure.Tasks; |
|||
using TagLib; |
|||
using TagLib.Image; |
|||
using static TagLib.File; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets |
|||
{ |
|||
public sealed class FileTagAssetMetadataSource : IAssetMetadataSource |
|||
{ |
|||
private sealed class FileAbstraction : IFileAbstraction |
|||
{ |
|||
private readonly AssetFile file; |
|||
|
|||
public string Name |
|||
{ |
|||
get { return file.FileName; } |
|||
} |
|||
|
|||
public Stream ReadStream |
|||
{ |
|||
get { return file.OpenRead(); } |
|||
} |
|||
|
|||
public Stream WriteStream |
|||
{ |
|||
get { throw new NotSupportedException(); } |
|||
} |
|||
|
|||
public FileAbstraction(AssetFile file) |
|||
{ |
|||
this.file = file; |
|||
} |
|||
|
|||
public void CloseStream(Stream stream) |
|||
{ |
|||
stream.Close(); |
|||
} |
|||
} |
|||
|
|||
public Task EnhanceAsync(UploadAssetCommand command, HashSet<string>? tags) |
|||
{ |
|||
Enhance(command, tags); |
|||
|
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
private void Enhance(UploadAssetCommand command, HashSet<string>? tags) |
|||
{ |
|||
try |
|||
{ |
|||
using (var file = Create(new FileAbstraction(command.File), ReadStyle.Average)) |
|||
{ |
|||
if (file.Properties == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var type = file.Properties.MediaTypes; |
|||
|
|||
if (type == MediaTypes.Audio) |
|||
{ |
|||
command.Type = AssetType.Audio; |
|||
} |
|||
else if (type == MediaTypes.Photo) |
|||
{ |
|||
command.Type = AssetType.Image; |
|||
} |
|||
else if (type.HasFlag(MediaTypes.Video)) |
|||
{ |
|||
command.Type = AssetType.Video; |
|||
} |
|||
|
|||
var pw = file.Properties.PhotoWidth; |
|||
var ph = file.Properties.PhotoHeight; |
|||
|
|||
if (pw > 0 && pw > 0) |
|||
{ |
|||
command.Metadata.SetPixelWidth(pw); |
|||
command.Metadata.SetPixelHeight(ph); |
|||
|
|||
if (tags != null) |
|||
{ |
|||
tags.Add("image"); |
|||
|
|||
var wh = pw + ph; |
|||
|
|||
if (wh > 2000) |
|||
{ |
|||
tags.Add("image/large"); |
|||
} |
|||
else if (wh > 1000) |
|||
{ |
|||
tags.Add("image/medium"); |
|||
} |
|||
else |
|||
{ |
|||
tags.Add("image/small"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void TryAddString(string name, string? value) |
|||
{ |
|||
if (!string.IsNullOrWhiteSpace(value)) |
|||
{ |
|||
command.Metadata.Add(name, JsonValue.Create(value)); |
|||
} |
|||
} |
|||
|
|||
void TryAddInt(string name, int? value) |
|||
{ |
|||
if (value > 0) |
|||
{ |
|||
command.Metadata.Add(name, JsonValue.Create(value)); |
|||
} |
|||
} |
|||
|
|||
void TryAddDouble(string name, double? value) |
|||
{ |
|||
if (value > 0) |
|||
{ |
|||
command.Metadata.Add(name, JsonValue.Create(value)); |
|||
} |
|||
} |
|||
|
|||
void TryAddTimeSpan(string name, TimeSpan value) |
|||
{ |
|||
if (value != TimeSpan.Zero) |
|||
{ |
|||
command.Metadata.Add(name, JsonValue.Create(value.ToString())); |
|||
} |
|||
} |
|||
|
|||
if (file.Tag is ImageTag imageTag) |
|||
{ |
|||
TryAddDouble("locationLatitude", imageTag.Latitude); |
|||
TryAddDouble("locationLongitude", imageTag.Longitude); |
|||
|
|||
TryAddString("created", imageTag.DateTime?.ToIso8601()); |
|||
} |
|||
|
|||
TryAddTimeSpan("duration", file.Properties.Duration); |
|||
|
|||
TryAddInt("audioBitrate", file.Properties.AudioBitrate); |
|||
TryAddInt("audioChannels", file.Properties.AudioChannels); |
|||
TryAddInt("audioSampleRate", file.Properties.AudioSampleRate); |
|||
TryAddInt("bitsPerSample", file.Properties.BitsPerSample); |
|||
TryAddInt("imageQuality", file.Properties.PhotoQuality); |
|||
TryAddInt("videoWidth", file.Properties.VideoWidth); |
|||
TryAddInt("videoHeight", file.Properties.VideoHeight); |
|||
|
|||
TryAddString("description", file.Properties.Description); |
|||
} |
|||
} |
|||
catch |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
public IEnumerable<string> Format(IAssetEntity asset) |
|||
{ |
|||
var metadata = asset.Metadata; |
|||
|
|||
switch (asset.Type) |
|||
{ |
|||
case AssetType.Image: |
|||
{ |
|||
if (metadata.TryGetNumber("pixelWidth", out var w) && |
|||
metadata.TryGetNumber("pixelHeight", out var h)) |
|||
{ |
|||
yield return $"{w}x{h}px"; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case AssetType.Video: |
|||
{ |
|||
if (metadata.TryGetNumber("videoWidth", out var w) && |
|||
metadata.TryGetNumber("videoHeight", out var h)) |
|||
{ |
|||
yield return $"{w}x{h}pt"; |
|||
} |
|||
|
|||
if (metadata.TryGetString("duration", out var duration)) |
|||
{ |
|||
yield return duration; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case AssetType.Audio: |
|||
{ |
|||
if (metadata.TryGetString("duration", out var duration)) |
|||
{ |
|||
yield return duration; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets |
|||
{ |
|||
public interface IAssetMetadataSource |
|||
{ |
|||
Task EnhanceAsync(UploadAssetCommand command, HashSet<string>? tags); |
|||
|
|||
IEnumerable<string> Format(IAssetEntity asset); |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.Assets; |
|||
using Squidex.Domain.Apps.Entities.Assets.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Assets; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Assets |
|||
{ |
|||
public sealed class ImageMetadataSource : IAssetMetadataSource |
|||
{ |
|||
private readonly IAssetThumbnailGenerator assetThumbnailGenerator; |
|||
|
|||
public ImageMetadataSource(IAssetThumbnailGenerator assetThumbnailGenerator) |
|||
{ |
|||
Guard.NotNull(assetThumbnailGenerator); |
|||
|
|||
this.assetThumbnailGenerator = assetThumbnailGenerator; |
|||
} |
|||
|
|||
public async Task EnhanceAsync(UploadAssetCommand command, HashSet<string>? tags) |
|||
{ |
|||
var imageInfo = await assetThumbnailGenerator.GetImageInfoAsync(command.File.OpenRead()); |
|||
|
|||
if (imageInfo != null) |
|||
{ |
|||
command.Type = AssetType.Image; |
|||
|
|||
command.Metadata.SetPixelWidth(imageInfo.PixelWidth); |
|||
command.Metadata.SetPixelHeight(imageInfo.PixelHeight); |
|||
|
|||
if (tags != null) |
|||
{ |
|||
tags.Add("image"); |
|||
|
|||
var wh = imageInfo.PixelWidth + imageInfo.PixelHeight; |
|||
|
|||
if (wh > 2000) |
|||
{ |
|||
tags.Add("image/large"); |
|||
} |
|||
else if (wh > 1000) |
|||
{ |
|||
tags.Add("image/medium"); |
|||
} |
|||
else |
|||
{ |
|||
tags.Add("image/small"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public IEnumerable<string> Format(IAssetEntity asset) |
|||
{ |
|||
if (asset.Type == AssetType.Image) |
|||
{ |
|||
yield return $"{asset.Metadata.GetPixelWidth()}x{asset.Metadata.GetPixelHeight()}px"; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,39 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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<CreateAsset> |
|||
{ |
|||
public void GenerateTags(CreateAsset source, HashSet<string> 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"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,82 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Domain.Apps.Events; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities |
|||
{ |
|||
public static class EntityMapper |
|||
{ |
|||
public static T Update<T>(this T entity, Envelope<IEvent> envelope, Action<SquidexEvent, T>? updater = null) where T : IEntity |
|||
{ |
|||
var @event = (SquidexEvent)envelope.Payload; |
|||
|
|||
var headers = envelope.Headers; |
|||
|
|||
SetId(entity, headers); |
|||
SetCreated(entity, headers); |
|||
SetCreatedBy(entity, @event); |
|||
SetLastModified(entity, headers); |
|||
SetLastModifiedBy(entity, @event); |
|||
SetVersion(entity, headers); |
|||
|
|||
updater?.Invoke(@event, entity); |
|||
|
|||
return entity; |
|||
} |
|||
|
|||
private static void SetId(IEntity entity, EnvelopeHeaders headers) |
|||
{ |
|||
if (entity is IUpdateableEntity updateable && updateable.Id == Guid.Empty) |
|||
{ |
|||
updateable.Id = headers.AggregateId(); |
|||
} |
|||
} |
|||
|
|||
private static void SetVersion(IEntity entity, EnvelopeHeaders headers) |
|||
{ |
|||
if (entity is IUpdateableEntityWithVersion updateable) |
|||
{ |
|||
updateable.Version = headers.EventStreamNumber(); |
|||
} |
|||
} |
|||
|
|||
private static void SetCreated(IEntity entity, EnvelopeHeaders headers) |
|||
{ |
|||
if (entity is IUpdateableEntity updateable && updateable.Created == default) |
|||
{ |
|||
updateable.Created = headers.Timestamp(); |
|||
} |
|||
} |
|||
|
|||
private static void SetCreatedBy(IEntity entity, SquidexEvent @event) |
|||
{ |
|||
if (entity is IUpdateableEntityWithCreatedBy withCreatedBy && withCreatedBy.CreatedBy == null) |
|||
{ |
|||
withCreatedBy.CreatedBy = @event.Actor; |
|||
} |
|||
} |
|||
|
|||
private static void SetLastModified(IEntity entity, EnvelopeHeaders headers) |
|||
{ |
|||
if (entity is IUpdateableEntity updateable) |
|||
{ |
|||
updateable.LastModified = headers.Timestamp(); |
|||
} |
|||
} |
|||
|
|||
private static void SetLastModifiedBy(IEntity entity, SquidexEvent @event) |
|||
{ |
|||
if (entity is IUpdateableEntityWithLastModifiedBy withModifiedBy) |
|||
{ |
|||
withModifiedBy.LastModifiedBy = @event.Actor; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using NodaTime; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities |
|||
{ |
|||
public interface IUpdateableEntity |
|||
{ |
|||
Guid Id { get; set; } |
|||
|
|||
Instant Created { get; set; } |
|||
|
|||
Instant LastModified { get; set; } |
|||
} |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities |
|||
{ |
|||
public interface IUpdateableEntityWithCreatedBy |
|||
{ |
|||
RefToken CreatedBy { get; set; } |
|||
} |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities |
|||
{ |
|||
public interface IUpdateableEntityWithLastModifiedBy |
|||
{ |
|||
RefToken LastModifiedBy { get; set; } |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue