diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs index 07b7eb57e..3597e55a3 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs @@ -53,6 +53,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets new CreateIndexModel( Index .Ascending(x => x.AppId) + .Ascending(x => x.IsDeleted) .Ascending(x => x.Slug)) }, ct); @@ -114,32 +115,22 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets using (Profiler.TraceMethod()) { var assetEntity = - await Collection.Find(x => x.IndexedAppId == appId && x.Slug == slug) + await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.Slug == slug) .FirstOrDefaultAsync(); - if (assetEntity?.IsDeleted == true) - { - return null; - } - return assetEntity; } } - public async Task FindAssetByHashAsync(Guid appId, string hash) + public async Task> QueryByHashAsync(Guid appId, string hash) { using (Profiler.TraceMethod()) { - var assetEntity = + var assetEntities = await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.FileHash == hash) - .FirstOrDefaultAsync(); - - if (assetEntity?.IsDeleted == true) - { - return null; - } + .ToListAsync(); - return assetEntity; + return assetEntities.OfType().ToList(); } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs index dd15cdd28..f5366299b 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs @@ -11,7 +11,6 @@ using System.Security.Cryptography; using System.Threading.Tasks; using Orleans; using Squidex.Domain.Apps.Entities.Assets.Commands; -using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Tags; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; @@ -22,13 +21,13 @@ namespace Squidex.Domain.Apps.Entities.Assets public sealed class AssetCommandMiddleware : GrainCommandMiddleware { private readonly IAssetStore assetStore; - private readonly AssetQueryService assetQueryService; + private readonly IAssetQueryService assetQueryService; private readonly IAssetThumbnailGenerator assetThumbnailGenerator; private readonly IEnumerable> tagGenerators; public AssetCommandMiddleware( IGrainFactory grainFactory, - AssetQueryService assetQueryService, + IAssetQueryService assetQueryService, IAssetStore assetStore, IAssetThumbnailGenerator assetThumbnailGenerator, IEnumerable> tagGenerators) @@ -63,21 +62,27 @@ namespace Squidex.Domain.Apps.Entities.Assets try { - var existing = await assetQueryService.FindAssetByHashAsync(createAsset.AppId.Id, createAsset.FileHash); + var existings = await assetQueryService.QueryByHashAsync(createAsset.AppId.Id, createAsset.FileHash); - AssetCreatedResult result; + AssetCreatedResult result = null; - if (IsDuplicate(createAsset, existing)) + foreach (var existing in existings) { - result = new AssetCreatedResult( - existing.Id, - existing.Tags, - existing.Version, - existing.FileVersion, - existing.FileHash, - true); + if (IsDuplicate(createAsset, existing)) + { + result = new AssetCreatedResult( + existing.Id, + existing.Tags, + existing.Version, + existing.FileVersion, + existing.FileHash, + true); + } + + break; } - else + + if (result == null) { foreach (var tagGenerator in tagGenerators) { @@ -147,7 +152,7 @@ namespace Squidex.Domain.Apps.Entities.Assets { await assetStore.UploadAsync(context.ContextId.ToString(), hashStream); - hash = hashStream.GetHashStringAndReset(); + hash = $"{hashStream.GetHashStringAndReset()}{file.FileName}{file.FileSize}".Sha256Base64(); } return hash; diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs index 5965d1030..9edfe22fb 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs @@ -38,14 +38,14 @@ namespace Squidex.Domain.Apps.Entities.Assets this.tagService = tagService; } - public virtual Task FindAssetAsync(QueryContext context, Guid id) + public Task FindAssetAsync(QueryContext context, Guid id) { Guard.NotNull(context, nameof(context)); return FindAssetAsync(context.App.Id, id); } - public virtual async Task FindAssetAsync(Guid appId, Guid id) + public async Task FindAssetAsync(Guid appId, Guid id) { var asset = await assetRepository.FindAssetAsync(id); @@ -57,19 +57,18 @@ namespace Squidex.Domain.Apps.Entities.Assets return asset; } - public virtual async Task FindAssetByHashAsync(Guid appId, string hash) + public async Task> QueryByHashAsync(Guid appId, string hash) { - var asset = await assetRepository.FindAssetByHashAsync(appId, hash); + Guard.NotNull(hash, nameof(hash)); - if (asset != null) - { - await DenormalizeTagsAsync(appId, Enumerable.Repeat(asset, 1)); - } + var assets = await assetRepository.QueryByHashAsync(appId, hash); - return asset; + await DenormalizeTagsAsync(appId, assets); + + return assets; } - public virtual async Task> QueryAsync(QueryContext context, Q query) + public async Task> QueryAsync(QueryContext context, Q query) { Guard.NotNull(context, nameof(context)); Guard.NotNull(query, nameof(query)); diff --git a/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs b/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs index fa17c0731..f93f0f69b 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Threading.Tasks; using Squidex.Infrastructure; @@ -13,6 +14,8 @@ namespace Squidex.Domain.Apps.Entities.Assets { public interface IAssetQueryService { + Task> QueryByHashAsync(Guid appId, string hash); + Task> QueryAsync(QueryContext contex, Q query); Task FindAssetAsync(QueryContext context, Guid id); diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs b/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs index 81481f99b..12de8c72a 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs @@ -15,6 +15,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.Repositories { public interface IAssetRepository { + Task> QueryByHashAsync(Guid appId, string hash); + Task> QueryAsync(Guid appId, Query query); Task> QueryAsync(Guid appId, HashSet ids); @@ -23,8 +25,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.Repositories Task FindAssetBySlugAsync(Guid appId, string slug); - Task FindAssetByHashAsync(Guid appId, string hash); - Task RemoveAsync(Guid appId); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs index b5d470564..23417373c 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs @@ -101,10 +101,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { - Name = "slug", + Name = "fileHash", ResolvedType = AllTypes.NonNullString, - Resolver = Resolve(x => x.Slug), - Description = "The file name as slug." + Resolver = Resolve(x => x.FileHash), + Description = "The hash of the file. Can be null for old files." }); AddField(new FieldType @@ -131,6 +131,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types Description = "The version of the file." }); + AddField(new FieldType + { + Name = "slug", + ResolvedType = AllTypes.NonNullString, + Resolver = Resolve(x => x.Slug), + Description = "The file name as slug." + }); + AddField(new FieldType { Name = "isImage", diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs index 056250ff0..b7bd86d8e 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs @@ -108,7 +108,7 @@ namespace Squidex.Areas.Api.Controllers.Assets } else { - entity = await assetRepository.FindAssetByHashAsync(App.Id, idOrSlug); + entity = await assetRepository.FindAssetBySlugAsync(App.Id, idOrSlug); } return DeliverAsset(entity, version, width, height, quality, mode); diff --git a/src/Squidex/app/features/settings/pages/backups/backups-page.component.html b/src/Squidex/app/features/settings/pages/backups/backups-page.component.html index 53d3871db..dddcf8cf6 100644 --- a/src/Squidex/app/features/settings/pages/backups/backups-page.component.html +++ b/src/Squidex/app/features/settings/pages/backups/backups-page.component.html @@ -40,7 +40,7 @@
-
+
diff --git a/src/Squidex/app/framework/utils/rxjs-extensions.ts b/src/Squidex/app/framework/utils/rxjs-extensions.ts index b93664ec6..a0ad45487 100644 --- a/src/Squidex/app/framework/utils/rxjs-extensions.ts +++ b/src/Squidex/app/framework/utils/rxjs-extensions.ts @@ -52,4 +52,12 @@ export function switchSafe(project: (source: T) => Observable) { return function mapOperation(source: Observable) { return source.pipe(switchMap(project), onErrorResumeNext()); }; +} + +export function ofForever(...values: T[]) { + return new Observable(s => { + for (let value of values) { + s.next(value); + } + }); } \ No newline at end of file diff --git a/src/Squidex/app/shared/components/asset-uploader.component.html b/src/Squidex/app/shared/components/asset-uploader.component.html index abb338d95..57a00d5c7 100644 --- a/src/Squidex/app/shared/components/asset-uploader.component.html +++ b/src/Squidex/app/shared/components/asset-uploader.component.html @@ -12,12 +12,23 @@ No upload in progress, drop files here. -
+
+
+
+ +
+
+ +
+
+ +
+
{{upload.name}}
- +