mirror of https://github.com/Squidex/squidex.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
6.9 KiB
200 lines
6.9 KiB
// ==========================================================================
|
|
// Squidex Headless CMS
|
|
// ==========================================================================
|
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
// All rights reserved. Licensed under the MIT license.
|
|
// ==========================================================================
|
|
|
|
using Squidex.Assets;
|
|
using Squidex.Domain.Apps.Core.Tags;
|
|
using Squidex.Domain.Apps.Entities.Assets.DomainObject;
|
|
using Squidex.Domain.Apps.Entities.Backup;
|
|
using Squidex.Domain.Apps.Events.Assets;
|
|
using Squidex.Infrastructure;
|
|
using Squidex.Infrastructure.Commands;
|
|
using Squidex.Infrastructure.EventSourcing;
|
|
|
|
namespace Squidex.Domain.Apps.Entities.Assets
|
|
{
|
|
public sealed class BackupAssets : IBackupHandler
|
|
{
|
|
private const int BatchSize = 100;
|
|
private const string TagsFile = "AssetTags.json";
|
|
private const string TagsAliasFile = "AssetTagsAlias.json";
|
|
private readonly HashSet<DomainId> assetIds = new HashSet<DomainId>();
|
|
private readonly HashSet<DomainId> assetFolderIds = new HashSet<DomainId>();
|
|
private readonly Rebuilder rebuilder;
|
|
private readonly IAssetFileStore assetFileStore;
|
|
private readonly ITagService tagService;
|
|
|
|
public string Name { get; } = "Assets";
|
|
|
|
public BackupAssets(Rebuilder rebuilder, IAssetFileStore assetFileStore, ITagService tagService)
|
|
{
|
|
this.rebuilder = rebuilder;
|
|
this.assetFileStore = assetFileStore;
|
|
this.tagService = tagService;
|
|
}
|
|
|
|
public Task BackupAsync(BackupContext context,
|
|
CancellationToken ct)
|
|
{
|
|
return BackupTagsAsync(context, ct);
|
|
}
|
|
|
|
public Task BackupEventAsync(Envelope<IEvent> @event, BackupContext context,
|
|
CancellationToken ct)
|
|
{
|
|
switch (@event.Payload)
|
|
{
|
|
case AssetCreated assetCreated:
|
|
return WriteAssetAsync(
|
|
assetCreated.AppId.Id,
|
|
assetCreated.AssetId,
|
|
assetCreated.FileVersion,
|
|
context.Writer,
|
|
ct);
|
|
case AssetUpdated assetUpdated:
|
|
return WriteAssetAsync(
|
|
assetUpdated.AppId.Id,
|
|
assetUpdated.AssetId,
|
|
assetUpdated.FileVersion,
|
|
context.Writer,
|
|
ct);
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public async Task<bool> RestoreEventAsync(Envelope<IEvent> @event, RestoreContext context,
|
|
CancellationToken ct)
|
|
{
|
|
switch (@event.Payload)
|
|
{
|
|
case AssetFolderCreated:
|
|
assetFolderIds.Add(@event.Headers.AggregateId());
|
|
break;
|
|
case AssetCreated assetCreated:
|
|
assetIds.Add(@event.Headers.AggregateId());
|
|
|
|
await ReadAssetAsync(
|
|
assetCreated.AppId.Id,
|
|
assetCreated.AssetId,
|
|
assetCreated.FileVersion,
|
|
context.Reader,
|
|
ct);
|
|
break;
|
|
case AssetUpdated assetUpdated:
|
|
await ReadAssetAsync(
|
|
assetUpdated.AppId.Id,
|
|
assetUpdated.AssetId,
|
|
assetUpdated.FileVersion,
|
|
context.Reader,
|
|
ct);
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public async Task RestoreAsync(RestoreContext context,
|
|
CancellationToken ct)
|
|
{
|
|
await RestoreTagsAsync(context, ct);
|
|
|
|
if (assetIds.Count > 0)
|
|
{
|
|
await rebuilder.InsertManyAsync<AssetDomainObject, AssetDomainObject.State>(assetIds, BatchSize, ct);
|
|
}
|
|
|
|
if (assetFolderIds.Count > 0)
|
|
{
|
|
await rebuilder.InsertManyAsync<AssetFolderDomainObject, AssetFolderDomainObject.State>(assetFolderIds, BatchSize, ct);
|
|
}
|
|
}
|
|
|
|
private async Task RestoreTagsAsync(RestoreContext context,
|
|
CancellationToken ct)
|
|
{
|
|
var tags = (Dictionary<string, Tag>?)null;
|
|
|
|
if (await context.Reader.HasFileAsync(TagsFile, ct))
|
|
{
|
|
tags = await context.Reader.ReadJsonAsync<Dictionary<string, Tag>>(TagsFile, ct);
|
|
}
|
|
|
|
var alias = (Dictionary<string, string>?)null;
|
|
|
|
if (await context.Reader.HasFileAsync(TagsAliasFile, ct))
|
|
{
|
|
alias = await context.Reader.ReadJsonAsync<Dictionary<string, string>>(TagsAliasFile, ct);
|
|
}
|
|
|
|
if (alias == null && tags == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var export = new TagsExport { Tags = tags, Alias = alias };
|
|
|
|
await tagService.RebuildTagsAsync(context.AppId, TagGroups.Assets, export, ct);
|
|
}
|
|
|
|
private async Task BackupTagsAsync(BackupContext context,
|
|
CancellationToken ct)
|
|
{
|
|
var tags = await tagService.GetExportableTagsAsync(context.AppId, TagGroups.Assets, ct);
|
|
|
|
if (tags.Tags != null)
|
|
{
|
|
await context.Writer.WriteJsonAsync(TagsFile, tags.Tags, ct);
|
|
}
|
|
|
|
if (tags.Alias?.Count > 0)
|
|
{
|
|
await context.Writer.WriteJsonAsync(TagsAliasFile, tags.Alias, ct);
|
|
}
|
|
}
|
|
|
|
private async Task WriteAssetAsync(DomainId appId, DomainId assetId, long fileVersion, IBackupWriter writer,
|
|
CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
var fileName = GetName(assetId, fileVersion);
|
|
|
|
await using (var stream = await writer.OpenBlobAsync(fileName, ct))
|
|
{
|
|
await assetFileStore.DownloadAsync(appId, assetId, fileVersion, null, stream, default, ct);
|
|
}
|
|
}
|
|
catch (AssetNotFoundException)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
private async Task ReadAssetAsync(DomainId appId, DomainId assetId, long fileVersion, IBackupReader reader,
|
|
CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
var fileName = GetName(assetId, fileVersion);
|
|
|
|
await using (var stream = await reader.OpenBlobAsync(fileName, ct))
|
|
{
|
|
await assetFileStore.UploadAsync(appId, assetId, fileVersion, null, stream, true, ct);
|
|
}
|
|
}
|
|
catch (FileNotFoundException)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
private static string GetName(DomainId assetId, long fileVersion)
|
|
{
|
|
return $"{assetId}_{fileVersion}.asset";
|
|
}
|
|
}
|
|
}
|
|
|