mirror of https://github.com/Squidex/squidex.git
Browse Source
* Configure Squidex as resize server. * Make tests with resize server. * Temp * User image controller.pull/816/head
committed by
GitHub
31 changed files with 448 additions and 382 deletions
@ -0,0 +1,162 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.Net.Http.Headers; |
||||
|
using Squidex.Assets; |
||||
|
using Squidex.Domain.Apps.Entities; |
||||
|
using Squidex.Domain.Apps.Entities.Apps; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Web; |
||||
|
|
||||
|
namespace Squidex.Areas.Api.Controllers.Apps |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Manages and configures apps.
|
||||
|
/// </summary>
|
||||
|
[ApiExplorerSettings(GroupName = nameof(Apps))] |
||||
|
public sealed class AppImageController : ApiController |
||||
|
{ |
||||
|
private readonly IAppImageStore appImageStore; |
||||
|
private readonly IAppProvider appProvider; |
||||
|
private readonly IAssetStore assetStore; |
||||
|
private readonly IAssetThumbnailGenerator assetThumbnailGenerator; |
||||
|
|
||||
|
public AppImageController(ICommandBus commandBus, |
||||
|
IAppImageStore appImageStore, |
||||
|
IAppProvider appProvider, |
||||
|
IAssetStore assetStore, |
||||
|
IAssetThumbnailGenerator assetThumbnailGenerator) |
||||
|
: base(commandBus) |
||||
|
{ |
||||
|
this.appImageStore = appImageStore; |
||||
|
this.appProvider = appProvider; |
||||
|
this.assetStore = assetStore; |
||||
|
this.assetThumbnailGenerator = assetThumbnailGenerator; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get the app image.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The name of the app.</param>
|
||||
|
/// <returns>
|
||||
|
/// 200 => App image found and content or (resized) image returned.
|
||||
|
/// 404 => App not found.
|
||||
|
/// </returns>
|
||||
|
[HttpGet] |
||||
|
[Route("apps/{app}/image")] |
||||
|
[ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)] |
||||
|
[AllowAnonymous] |
||||
|
[ApiCosts(0)] |
||||
|
public IActionResult GetImage(string app) |
||||
|
{ |
||||
|
if (App.Image == null) |
||||
|
{ |
||||
|
return NotFound(); |
||||
|
} |
||||
|
|
||||
|
var etag = App.Image.Etag; |
||||
|
|
||||
|
Response.Headers[HeaderNames.ETag] = etag; |
||||
|
|
||||
|
var callback = new FileCallback(async (body, range, ct) => |
||||
|
{ |
||||
|
var resizedAsset = $"{App.Id}_{etag}_Resized"; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
await assetStore.DownloadAsync(resizedAsset, body, ct: ct); |
||||
|
} |
||||
|
catch (AssetNotFoundException) |
||||
|
{ |
||||
|
await ResizeAsync(resizedAsset, App.Image.MimeType, body, ct); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
return new FileCallbackResult(App.Image.MimeType, callback) |
||||
|
{ |
||||
|
ErrorAs404 = true |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private async Task ResizeAsync(string resizedAsset, string mimeType, Stream target, |
||||
|
CancellationToken ct) |
||||
|
{ |
||||
|
#pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods
|
||||
|
#pragma warning disable MA0040 // Flow the cancellation token
|
||||
|
using var activity = Telemetry.Activities.StartActivity("Resize"); |
||||
|
|
||||
|
await using var assetOriginal = new TempAssetFile(resizedAsset, mimeType, 0); |
||||
|
await using var assetResized = new TempAssetFile(resizedAsset, mimeType, 0); |
||||
|
|
||||
|
var resizeOptions = new ResizeOptions |
||||
|
{ |
||||
|
TargetWidth = 50, |
||||
|
TargetHeight = 50 |
||||
|
}; |
||||
|
|
||||
|
using (Telemetry.Activities.StartActivity("Read")) |
||||
|
{ |
||||
|
await using (var originalStream = assetOriginal.OpenWrite()) |
||||
|
{ |
||||
|
await appImageStore.DownloadAsync(App.Id, originalStream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
using (Telemetry.Activities.StartActivity("Resize")) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await using (var originalStream = assetOriginal.OpenRead()) |
||||
|
{ |
||||
|
await using (var resizeStream = assetResized.OpenWrite()) |
||||
|
{ |
||||
|
await assetThumbnailGenerator.CreateThumbnailAsync(originalStream, mimeType, resizeStream, resizeOptions); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
await using (var originalStream = assetOriginal.OpenRead()) |
||||
|
{ |
||||
|
await using (var resizeStream = assetResized.OpenWrite()) |
||||
|
{ |
||||
|
await originalStream.CopyToAsync(resizeStream); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
using (Telemetry.Activities.StartActivity("Save")) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await using (var resizeStream = assetResized.OpenRead()) |
||||
|
{ |
||||
|
await assetStore.UploadAsync(resizedAsset, resizeStream); |
||||
|
} |
||||
|
} |
||||
|
catch (AssetAlreadyExistsException) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
using (Telemetry.Activities.StartActivity("Write")) |
||||
|
{ |
||||
|
await using (var resizeStream = assetResized.OpenRead()) |
||||
|
{ |
||||
|
await resizeStream.CopyToAsync(target, ct); |
||||
|
} |
||||
|
} |
||||
|
#pragma warning restore CA2016 // Forward the 'CancellationToken' parameter to methods
|
||||
|
#pragma warning restore MA0040 // Flow the cancellation token
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Assets; |
||||
|
using Squidex.Assets.ImageMagick; |
||||
|
using Squidex.Assets.ImageSharp; |
||||
|
using Squidex.Assets.Remote; |
||||
|
|
||||
|
namespace Squidex.Config.Domain |
||||
|
{ |
||||
|
public static class ResizeServices |
||||
|
{ |
||||
|
public static void AddSquidexImageResizing(this IServiceCollection services, IConfiguration config) |
||||
|
{ |
||||
|
var thumbnailGenerator = new CompositeThumbnailGenerator( |
||||
|
new IAssetThumbnailGenerator[] |
||||
|
{ |
||||
|
new ImageSharpThumbnailGenerator(), |
||||
|
new ImageMagickThumbnailGenerator() |
||||
|
}); |
||||
|
|
||||
|
var resizerUrl = config.GetValue<string>("assets:resizerUrl"); |
||||
|
|
||||
|
if (!string.IsNullOrWhiteSpace(resizerUrl)) |
||||
|
{ |
||||
|
services.AddHttpClient("Resize", options => |
||||
|
{ |
||||
|
options.BaseAddress = new Uri(resizerUrl); |
||||
|
}); |
||||
|
|
||||
|
services.AddSingletonAs(c => new RemoteThumbnailGenerator(c.GetRequiredService<IHttpClientFactory>(), thumbnailGenerator)) |
||||
|
.As<IAssetThumbnailGenerator>(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
services.AddSingletonAs(c => thumbnailGenerator) |
||||
|
.As<IAssetThumbnailGenerator>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,59 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Newtonsoft.Json; |
|
||||
using Squidex.Areas.Api.Controllers.Rules.Models; |
|
||||
using Squidex.Domain.Apps.Core.HandleRules; |
|
||||
using Squidex.Hosting; |
|
||||
using Squidex.Infrastructure.Json; |
|
||||
using Squidex.Infrastructure.MongoDb; |
|
||||
using Squidex.Infrastructure.Orleans; |
|
||||
|
|
||||
namespace Squidex.Config.Domain |
|
||||
{ |
|
||||
public sealed class SerializationInitializer : IInitializable |
|
||||
{ |
|
||||
private readonly JsonSerializer jsonNetSerializer; |
|
||||
private readonly IJsonSerializer jsonSerializer; |
|
||||
private readonly RuleRegistry ruleRegistry; |
|
||||
|
|
||||
public int Order => -1; |
|
||||
|
|
||||
public SerializationInitializer(JsonSerializer jsonNetSerializer, IJsonSerializer jsonSerializer, RuleRegistry ruleRegistry) |
|
||||
{ |
|
||||
this.jsonNetSerializer = jsonNetSerializer; |
|
||||
this.jsonSerializer = jsonSerializer; |
|
||||
|
|
||||
this.ruleRegistry = ruleRegistry; |
|
||||
} |
|
||||
|
|
||||
public Task InitializeAsync( |
|
||||
CancellationToken ct) |
|
||||
{ |
|
||||
SetupBson(); |
|
||||
SetupOrleans(); |
|
||||
SetupActions(); |
|
||||
|
|
||||
return Task.CompletedTask; |
|
||||
} |
|
||||
|
|
||||
private void SetupActions() |
|
||||
{ |
|
||||
RuleActionConverter.Mapping = ruleRegistry.Actions.ToDictionary(x => x.Key, x => x.Value.Type); |
|
||||
} |
|
||||
|
|
||||
private void SetupBson() |
|
||||
{ |
|
||||
BsonJsonConvention.Register(jsonNetSerializer); |
|
||||
} |
|
||||
|
|
||||
private void SetupOrleans() |
|
||||
{ |
|
||||
J.DefaultSerializer = jsonSerializer; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue