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