mirror of https://github.com/Squidex/squidex.git
241 changed files with 222 additions and 5564 deletions
@ -1,164 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Azure.Storage; |
|||
using Microsoft.Azure.Storage.Blob; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public class AzureBlobAssetStore : IAssetStore, IInitializable |
|||
{ |
|||
private readonly string containerName; |
|||
private readonly string connectionString; |
|||
private CloudBlobContainer blobContainer; |
|||
|
|||
public AzureBlobAssetStore(string connectionString, string containerName) |
|||
{ |
|||
Guard.NotNullOrEmpty(containerName, nameof(containerName)); |
|||
Guard.NotNullOrEmpty(connectionString, nameof(connectionString)); |
|||
|
|||
this.connectionString = connectionString; |
|||
this.containerName = containerName; |
|||
} |
|||
|
|||
public async Task InitializeAsync(CancellationToken ct = default) |
|||
{ |
|||
try |
|||
{ |
|||
var storageAccount = CloudStorageAccount.Parse(connectionString); |
|||
|
|||
var blobClient = storageAccount.CreateCloudBlobClient(); |
|||
var blobReference = blobClient.GetContainerReference(containerName); |
|||
|
|||
await blobReference.CreateIfNotExistsAsync(ct); |
|||
|
|||
blobContainer = blobReference; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
throw new ConfigurationException($"Cannot connect to blob container '{containerName}'.", ex); |
|||
} |
|||
} |
|||
|
|||
public string? GeneratePublicUrl(string fileName) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
|
|||
if (blobContainer.Properties.PublicAccess != BlobContainerPublicAccessType.Blob) |
|||
{ |
|||
var blob = blobContainer.GetBlockBlobReference(fileName); |
|||
|
|||
return blob.Uri.ToString(); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public async Task<long> GetSizeAsync(string fileName, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
|
|||
try |
|||
{ |
|||
var blob = blobContainer.GetBlockBlobReference(fileName); |
|||
|
|||
await blob.FetchAttributesAsync(ct); |
|||
|
|||
return blob.Properties.Length; |
|||
} |
|||
catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 404) |
|||
{ |
|||
throw new AssetNotFoundException(fileName, ex); |
|||
} |
|||
} |
|||
|
|||
public async Task CopyAsync(string sourceFileName, string targetFileName, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(sourceFileName, nameof(sourceFileName)); |
|||
Guard.NotNullOrEmpty(targetFileName, nameof(targetFileName)); |
|||
|
|||
try |
|||
{ |
|||
var sourceBlob = blobContainer.GetBlockBlobReference(sourceFileName); |
|||
|
|||
var targetBlob = blobContainer.GetBlobReference(targetFileName); |
|||
|
|||
await targetBlob.StartCopyAsync(sourceBlob.Uri, null, AccessCondition.GenerateIfNotExistsCondition(), null, null, ct); |
|||
|
|||
while (targetBlob.CopyState.Status == CopyStatus.Pending) |
|||
{ |
|||
ct.ThrowIfCancellationRequested(); |
|||
|
|||
await Task.Delay(50, ct); |
|||
await targetBlob.FetchAttributesAsync(null, null, null, ct); |
|||
} |
|||
|
|||
if (targetBlob.CopyState.Status != CopyStatus.Success) |
|||
{ |
|||
throw new StorageException($"Copy of temporary file failed: {targetBlob.CopyState.Status}"); |
|||
} |
|||
} |
|||
catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 409) |
|||
{ |
|||
throw new AssetAlreadyExistsException(targetFileName, ex); |
|||
} |
|||
catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 404) |
|||
{ |
|||
throw new AssetNotFoundException(sourceFileName, ex); |
|||
} |
|||
} |
|||
|
|||
public async Task DownloadAsync(string fileName, Stream stream, BytesRange range = default, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
try |
|||
{ |
|||
var blob = blobContainer.GetBlockBlobReference(fileName); |
|||
|
|||
using (var blobStream = await blob.OpenReadAsync(null, null, null, ct)) |
|||
{ |
|||
await blobStream.CopyToAsync(stream, range, ct); |
|||
} |
|||
} |
|||
catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 404) |
|||
{ |
|||
throw new AssetNotFoundException(fileName, ex); |
|||
} |
|||
} |
|||
|
|||
public async Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
|
|||
try |
|||
{ |
|||
var tempBlob = blobContainer.GetBlockBlobReference(fileName); |
|||
|
|||
await tempBlob.UploadFromStreamAsync(stream, overwrite ? null : AccessCondition.GenerateIfNotExistsCondition(), null, null, ct); |
|||
} |
|||
catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 409) |
|||
{ |
|||
throw new AssetAlreadyExistsException(fileName, ex); |
|||
} |
|||
} |
|||
|
|||
public Task DeleteAsync(string fileName) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
|
|||
var blob = blobContainer.GetBlockBlobReference(fileName); |
|||
|
|||
return blob.DeleteIfExistsAsync(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,150 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Driver; |
|||
using MongoDB.Driver.GridFS; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public sealed class MongoGridFsAssetStore : IAssetStore, IInitializable |
|||
{ |
|||
private static readonly GridFSDownloadOptions DownloadDefault = new GridFSDownloadOptions(); |
|||
private static readonly GridFSDownloadOptions DownloadSeekable = new GridFSDownloadOptions { Seekable = true }; |
|||
private readonly IGridFSBucket<string> bucket; |
|||
|
|||
public MongoGridFsAssetStore(IGridFSBucket<string> bucket) |
|||
{ |
|||
Guard.NotNull(bucket, nameof(bucket)); |
|||
|
|||
this.bucket = bucket; |
|||
} |
|||
|
|||
public async Task InitializeAsync(CancellationToken ct = default) |
|||
{ |
|||
try |
|||
{ |
|||
await bucket.Database.ListCollectionsAsync(cancellationToken: ct); |
|||
} |
|||
catch (MongoException ex) |
|||
{ |
|||
throw new ConfigurationException($"Cannot connect to Mongo GridFS bucket '{bucket.Options.BucketName}'.", ex); |
|||
} |
|||
} |
|||
|
|||
public string? GeneratePublicUrl(string fileName) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
public async Task<long> GetSizeAsync(string fileName, CancellationToken ct = default) |
|||
{ |
|||
var name = GetFileName(fileName, nameof(fileName)); |
|||
|
|||
var query = await bucket.FindAsync(Builders<GridFSFileInfo<string>>.Filter.Eq(x => x.Id, name), cancellationToken: ct); |
|||
|
|||
var file = await query.FirstOrDefaultAsync(cancellationToken: ct); |
|||
|
|||
if (file == null) |
|||
{ |
|||
throw new AssetNotFoundException(fileName); |
|||
} |
|||
|
|||
return file.Length; |
|||
} |
|||
|
|||
public async Task CopyAsync(string sourceFileName, string targetFileName, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(targetFileName, nameof(targetFileName)); |
|||
|
|||
try |
|||
{ |
|||
var sourceName = GetFileName(sourceFileName, nameof(sourceFileName)); |
|||
|
|||
await using (var readStream = await bucket.OpenDownloadStreamAsync(sourceName, cancellationToken: ct)) |
|||
{ |
|||
await UploadAsync(targetFileName, readStream, false, ct); |
|||
} |
|||
} |
|||
catch (GridFSFileNotFoundException ex) |
|||
{ |
|||
throw new AssetNotFoundException(sourceFileName, ex); |
|||
} |
|||
} |
|||
|
|||
public async Task DownloadAsync(string fileName, Stream stream, BytesRange range, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
try |
|||
{ |
|||
var name = GetFileName(fileName, nameof(fileName)); |
|||
|
|||
var options = range.IsDefined ? DownloadSeekable : DownloadDefault; |
|||
|
|||
using (var readStream = await bucket.OpenDownloadStreamAsync(name, options, ct)) |
|||
{ |
|||
await readStream.CopyToAsync(stream, range, ct); |
|||
} |
|||
} |
|||
catch (GridFSFileNotFoundException ex) |
|||
{ |
|||
throw new AssetNotFoundException(fileName, ex); |
|||
} |
|||
} |
|||
|
|||
public async Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
try |
|||
{ |
|||
var name = GetFileName(fileName, nameof(fileName)); |
|||
|
|||
if (overwrite) |
|||
{ |
|||
await DeleteAsync(fileName); |
|||
} |
|||
|
|||
await bucket.UploadFromStreamAsync(name, name, stream, cancellationToken: ct); |
|||
} |
|||
catch (MongoWriteException ex) when (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey) |
|||
{ |
|||
throw new AssetAlreadyExistsException(fileName); |
|||
} |
|||
catch (MongoBulkWriteException<BsonDocument> ex) when (ex.WriteErrors.Any(x => x.Category == ServerErrorCategory.DuplicateKey)) |
|||
{ |
|||
throw new AssetAlreadyExistsException(fileName); |
|||
} |
|||
} |
|||
|
|||
public async Task DeleteAsync(string fileName) |
|||
{ |
|||
try |
|||
{ |
|||
var name = GetFileName(fileName, nameof(fileName)); |
|||
|
|||
await bucket.DeleteAsync(name); |
|||
} |
|||
catch (GridFSFileNotFoundException) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
private static string GetFileName(string fileName, string parameterName) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, parameterName); |
|||
|
|||
return fileName; |
|||
} |
|||
} |
|||
} |
|||
@ -1,33 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Runtime.Serialization; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
[Serializable] |
|||
public class AssetAlreadyExistsException : Exception |
|||
{ |
|||
public AssetAlreadyExistsException(string fileName, Exception? inner = null) |
|||
: base(FormatMessage(fileName), inner) |
|||
{ |
|||
} |
|||
|
|||
protected AssetAlreadyExistsException(SerializationInfo info, StreamingContext context) |
|||
: base(info, context) |
|||
{ |
|||
} |
|||
|
|||
private static string FormatMessage(string fileName) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
|
|||
return $"An asset with name '{fileName}' already exists."; |
|||
} |
|||
} |
|||
} |
|||
@ -1,39 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public abstract class AssetFile : IDisposable |
|||
{ |
|||
public string FileName { get; } |
|||
|
|||
public string MimeType { get; } |
|||
|
|||
public long FileSize { get; } |
|||
|
|||
protected AssetFile(string fileName, string mimeType, long fileSize) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); |
|||
Guard.GreaterEquals(fileSize, 0, nameof(fileSize)); |
|||
|
|||
FileName = fileName; |
|||
FileSize = fileSize; |
|||
|
|||
MimeType = mimeType; |
|||
} |
|||
|
|||
public virtual void Dispose() |
|||
{ |
|||
} |
|||
|
|||
public abstract Stream OpenRead(); |
|||
} |
|||
} |
|||
@ -1,33 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Runtime.Serialization; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
[Serializable] |
|||
public class AssetNotFoundException : Exception |
|||
{ |
|||
public AssetNotFoundException(string fileName, Exception? inner = null) |
|||
: base(FormatMessage(fileName), inner) |
|||
{ |
|||
} |
|||
|
|||
protected AssetNotFoundException(SerializationInfo info, StreamingContext context) |
|||
: base(info, context) |
|||
{ |
|||
} |
|||
|
|||
private static string FormatMessage(string fileName) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
|
|||
return $"An asset with name '{fileName}' does not exist."; |
|||
} |
|||
} |
|||
} |
|||
@ -1,28 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public sealed class DelegateAssetFile : AssetFile |
|||
{ |
|||
private readonly Func<Stream> openStream; |
|||
|
|||
public DelegateAssetFile(string fileName, string mimeType, long fileSize, Func<Stream> openStream) |
|||
: base(fileName, mimeType, fileSize) |
|||
{ |
|||
this.openStream = openStream; |
|||
} |
|||
|
|||
public override Stream OpenRead() |
|||
{ |
|||
return openStream(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,197 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using FluentFTP; |
|||
using Squidex.Infrastructure.Log; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
[ExcludeFromCodeCoverage] |
|||
public sealed class FTPAssetStore : IAssetStore, IInitializable |
|||
{ |
|||
private readonly string path; |
|||
private readonly ISemanticLog log; |
|||
private readonly Func<IFtpClient> factory; |
|||
|
|||
public FTPAssetStore(Func<IFtpClient> factory, string path, ISemanticLog log) |
|||
{ |
|||
Guard.NotNull(factory, nameof(factory)); |
|||
Guard.NotNullOrEmpty(path, nameof(path)); |
|||
Guard.NotNull(log, nameof(log)); |
|||
|
|||
this.factory = factory; |
|||
this.path = path; |
|||
|
|||
this.log = log; |
|||
} |
|||
|
|||
public async Task InitializeAsync(CancellationToken ct = default) |
|||
{ |
|||
using (var client = factory()) |
|||
{ |
|||
await client.ConnectAsync(ct); |
|||
|
|||
if (!await client.DirectoryExistsAsync(path, ct)) |
|||
{ |
|||
await client.CreateDirectoryAsync(path, ct); |
|||
} |
|||
} |
|||
|
|||
log.LogInformation(w => w |
|||
.WriteProperty("action", "FTPAssetStoreConfigured") |
|||
.WriteProperty("path", path)); |
|||
} |
|||
|
|||
public string? GeneratePublicUrl(string fileName) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
public async Task<long> GetSizeAsync(string fileName, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
|
|||
using (var client = GetFtpClient()) |
|||
{ |
|||
try |
|||
{ |
|||
var size = await client.GetFileSizeAsync(fileName, ct); |
|||
|
|||
if (size < 0) |
|||
{ |
|||
throw new AssetNotFoundException(fileName); |
|||
} |
|||
|
|||
return size; |
|||
} |
|||
catch (FtpException ex) when (IsNotFound(ex)) |
|||
{ |
|||
throw new AssetNotFoundException(fileName, ex); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public async Task CopyAsync(string sourceFileName, string targetFileName, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(sourceFileName, nameof(sourceFileName)); |
|||
Guard.NotNullOrEmpty(targetFileName, nameof(targetFileName)); |
|||
|
|||
using (var client = GetFtpClient()) |
|||
{ |
|||
var tempPath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); |
|||
|
|||
using (var stream = new FileStream(tempPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose)) |
|||
{ |
|||
try |
|||
{ |
|||
var found = await client.DownloadAsync(stream, sourceFileName, token: ct); |
|||
|
|||
if (!found) |
|||
{ |
|||
throw new AssetNotFoundException(sourceFileName); |
|||
} |
|||
} |
|||
catch (FtpException ex) when (IsNotFound(ex)) |
|||
{ |
|||
throw new AssetNotFoundException(sourceFileName, ex); |
|||
} |
|||
|
|||
await UploadAsync(client, targetFileName, stream, false, ct); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public async Task DownloadAsync(string fileName, Stream stream, BytesRange range = default, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
using (var client = GetFtpClient()) |
|||
{ |
|||
try |
|||
{ |
|||
using (var ftpStream = await client.OpenReadAsync(fileName, range.From ?? 0, ct)) |
|||
{ |
|||
await ftpStream.CopyToAsync(stream, range, ct, false); |
|||
} |
|||
} |
|||
catch (FtpException ex) when (IsNotFound(ex)) |
|||
{ |
|||
throw new AssetNotFoundException(fileName, ex); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public async Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
using (var client = GetFtpClient()) |
|||
{ |
|||
await UploadAsync(client, fileName, stream, overwrite, ct); |
|||
} |
|||
} |
|||
|
|||
private static async Task UploadAsync(IFtpClient client, string fileName, Stream stream, bool overwrite, CancellationToken ct) |
|||
{ |
|||
if (!overwrite && await client.FileExistsAsync(fileName, ct)) |
|||
{ |
|||
throw new AssetAlreadyExistsException(fileName); |
|||
} |
|||
|
|||
var mode = overwrite ? FtpRemoteExists.Overwrite : FtpRemoteExists.Skip; |
|||
|
|||
await client.UploadAsync(stream, fileName, mode, true, null, ct); |
|||
} |
|||
|
|||
public async Task DeleteAsync(string fileName) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
|
|||
using (var client = GetFtpClient()) |
|||
{ |
|||
try |
|||
{ |
|||
await client.DeleteFileAsync(fileName); |
|||
} |
|||
catch (FtpException ex) |
|||
{ |
|||
if (!IsNotFound(ex)) |
|||
{ |
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private IFtpClient GetFtpClient() |
|||
{ |
|||
var client = factory(); |
|||
|
|||
client.Connect(); |
|||
client.SetWorkingDirectory(path); |
|||
|
|||
return client; |
|||
} |
|||
|
|||
private static bool IsNotFound(Exception exception) |
|||
{ |
|||
if (exception is FtpCommandException command) |
|||
{ |
|||
return command.CompletionCode == "550"; |
|||
} |
|||
|
|||
return exception.InnerException != null && IsNotFound(exception.InnerException); |
|||
} |
|||
} |
|||
} |
|||
@ -1,152 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.Log; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public sealed class FolderAssetStore : IAssetStore, IInitializable |
|||
{ |
|||
private const int BufferSize = 81920; |
|||
private readonly ISemanticLog log; |
|||
private readonly DirectoryInfo directory; |
|||
|
|||
public FolderAssetStore(string path, ISemanticLog log) |
|||
{ |
|||
Guard.NotNullOrEmpty(path, nameof(path)); |
|||
Guard.NotNull(log, nameof(log)); |
|||
|
|||
this.log = log; |
|||
|
|||
directory = new DirectoryInfo(path); |
|||
} |
|||
|
|||
public Task InitializeAsync(CancellationToken ct = default) |
|||
{ |
|||
try |
|||
{ |
|||
if (!directory.Exists) |
|||
{ |
|||
directory.Create(); |
|||
} |
|||
|
|||
log.LogInformation(w => w |
|||
.WriteProperty("action", "FolderAssetStoreConfigured") |
|||
.WriteProperty("path", directory.FullName)); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
throw new ConfigurationException($"Cannot access directory {directory.FullName}", ex); |
|||
} |
|||
} |
|||
|
|||
public string? GeneratePublicUrl(string fileName) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
public Task<long> GetSizeAsync(string fileName, CancellationToken ct = default) |
|||
{ |
|||
var file = GetFile(fileName, nameof(fileName)); |
|||
|
|||
try |
|||
{ |
|||
return Task.FromResult(file.Length); |
|||
} |
|||
catch (FileNotFoundException ex) |
|||
{ |
|||
throw new AssetNotFoundException(fileName, ex); |
|||
} |
|||
} |
|||
|
|||
public Task CopyAsync(string sourceFileName, string targetFileName, CancellationToken ct = default) |
|||
{ |
|||
var targetFile = GetFile(targetFileName, nameof(targetFileName)); |
|||
var sourceFile = GetFile(sourceFileName, nameof(sourceFileName)); |
|||
|
|||
try |
|||
{ |
|||
sourceFile.CopyTo(targetFile.FullName); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
catch (IOException) when (targetFile.Exists) |
|||
{ |
|||
throw new AssetAlreadyExistsException(targetFileName); |
|||
} |
|||
catch (FileNotFoundException ex) |
|||
{ |
|||
throw new AssetNotFoundException(sourceFileName, ex); |
|||
} |
|||
} |
|||
|
|||
public async Task DownloadAsync(string fileName, Stream stream, BytesRange range, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
var file = GetFile(fileName, nameof(fileName)); |
|||
|
|||
try |
|||
{ |
|||
using (var fileStream = file.OpenRead()) |
|||
{ |
|||
await fileStream.CopyToAsync(stream, range, ct); |
|||
} |
|||
} |
|||
catch (FileNotFoundException ex) |
|||
{ |
|||
throw new AssetNotFoundException(fileName, ex); |
|||
} |
|||
} |
|||
|
|||
public async Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
var file = GetFile(fileName, nameof(fileName)); |
|||
|
|||
try |
|||
{ |
|||
using (var fileStream = file.Open(overwrite ? FileMode.Create : FileMode.CreateNew, FileAccess.Write)) |
|||
{ |
|||
await stream.CopyToAsync(fileStream, BufferSize, ct); |
|||
} |
|||
} |
|||
catch (IOException) when (file.Exists) |
|||
{ |
|||
throw new AssetAlreadyExistsException(file.Name); |
|||
} |
|||
} |
|||
|
|||
public Task DeleteAsync(string fileName) |
|||
{ |
|||
var file = GetFile(fileName, nameof(fileName)); |
|||
|
|||
file.Delete(); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private FileInfo GetFile(string fileName, string parameterName) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, parameterName); |
|||
|
|||
return new FileInfo(GetPath(fileName)); |
|||
} |
|||
|
|||
private string GetPath(string name) |
|||
{ |
|||
return Path.Combine(directory.FullName, name); |
|||
} |
|||
} |
|||
} |
|||
@ -1,101 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Security.Cryptography; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public sealed class HasherStream : Stream |
|||
{ |
|||
private readonly Stream inner; |
|||
private readonly IncrementalHash hasher; |
|||
|
|||
public override bool CanRead |
|||
{ |
|||
get { return inner.CanRead; } |
|||
} |
|||
|
|||
public override bool CanSeek |
|||
{ |
|||
get { return false; } |
|||
} |
|||
|
|||
public override bool CanWrite |
|||
{ |
|||
get { return false; } |
|||
} |
|||
|
|||
public override long Length |
|||
{ |
|||
get { return inner.Length; } |
|||
} |
|||
|
|||
public override long Position |
|||
{ |
|||
get { return inner.Position; } |
|||
set { throw new NotSupportedException(); } |
|||
} |
|||
|
|||
public HasherStream(Stream inner, HashAlgorithmName hashAlgorithmName) |
|||
{ |
|||
Guard.NotNull(inner, nameof(inner)); |
|||
|
|||
if (!inner.CanRead) |
|||
{ |
|||
throw new ArgumentException("Inner stream must be readable."); |
|||
} |
|||
|
|||
this.inner = inner; |
|||
|
|||
hasher = IncrementalHash.CreateHash(hashAlgorithmName); |
|||
} |
|||
|
|||
public override int Read(byte[] buffer, int offset, int count) |
|||
{ |
|||
var read = inner.Read(buffer, offset, count); |
|||
|
|||
if (read > 0) |
|||
{ |
|||
hasher.AppendData(buffer, offset, read); |
|||
} |
|||
|
|||
return read; |
|||
} |
|||
|
|||
public byte[] GetHashAndReset() |
|||
{ |
|||
return hasher.GetHashAndReset(); |
|||
} |
|||
|
|||
public string GetHashStringAndReset() |
|||
{ |
|||
return Convert.ToBase64String(GetHashAndReset()); |
|||
} |
|||
|
|||
public override void Flush() |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public override long Seek(long offset, SeekOrigin origin) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public override void SetLength(long value) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public override void Write(byte[] buffer, int offset, int count) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,28 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public interface IAssetStore |
|||
{ |
|||
string? GeneratePublicUrl(string fileName); |
|||
|
|||
Task<long> GetSizeAsync(string fileName, CancellationToken ct = default); |
|||
|
|||
Task CopyAsync(string sourceFileName, string targetFileName, CancellationToken ct = default); |
|||
|
|||
Task DownloadAsync(string fileName, Stream stream, BytesRange range = default, CancellationToken ct = default); |
|||
|
|||
Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default); |
|||
|
|||
Task DeleteAsync(string fileName); |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public interface IAssetThumbnailGenerator |
|||
{ |
|||
Task<ImageInfo?> GetImageInfoAsync(Stream source); |
|||
|
|||
Task<ImageInfo> FixOrientationAsync(Stream source, Stream destination); |
|||
|
|||
Task CreateThumbnailAsync(Stream source, Stream destination, ResizeOptions options); |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public enum ImageFormat |
|||
{ |
|||
Auto, |
|||
PNG, |
|||
JPEG, |
|||
TGA, |
|||
GIF |
|||
} |
|||
} |
|||
@ -1,31 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public sealed class ImageInfo |
|||
{ |
|||
public string? Format { get; set; } |
|||
|
|||
public int PixelWidth { get; } |
|||
|
|||
public int PixelHeight { get; } |
|||
|
|||
public bool IsRotatedOrSwapped { get; } |
|||
|
|||
public ImageInfo(int pixelWidth, int pixelHeight, bool isRotatedOrSwapped) |
|||
{ |
|||
Guard.GreaterThan(pixelWidth, 0, nameof(pixelWidth)); |
|||
Guard.GreaterThan(pixelHeight, 0, nameof(pixelHeight)); |
|||
|
|||
PixelWidth = pixelWidth; |
|||
PixelHeight = pixelHeight; |
|||
|
|||
IsRotatedOrSwapped = isRotatedOrSwapped; |
|||
} |
|||
} |
|||
} |
|||
@ -1,197 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.ImageSharp; |
|||
using SixLabors.ImageSharp.Formats; |
|||
using SixLabors.ImageSharp.Formats.Gif; |
|||
using SixLabors.ImageSharp.Formats.Jpeg; |
|||
using SixLabors.ImageSharp.Formats.Png; |
|||
using SixLabors.ImageSharp.Formats.Tga; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Exif; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using ISResizeMode = SixLabors.ImageSharp.Processing.ResizeMode; |
|||
using ISResizeOptions = SixLabors.ImageSharp.Processing.ResizeOptions; |
|||
|
|||
namespace Squidex.Infrastructure.Assets.ImageSharp |
|||
{ |
|||
public sealed class ImageSharpAssetThumbnailGenerator : IAssetThumbnailGenerator |
|||
{ |
|||
private readonly SemaphoreSlim maxTasks = new SemaphoreSlim(Math.Max(Environment.ProcessorCount / 4, 1)); |
|||
|
|||
public async Task CreateThumbnailAsync(Stream source, Stream destination, ResizeOptions options) |
|||
{ |
|||
Guard.NotNull(source, nameof(source)); |
|||
Guard.NotNull(destination, nameof(destination)); |
|||
Guard.NotNull(options, nameof(options)); |
|||
|
|||
if (!options.IsValid) |
|||
{ |
|||
await source.CopyToAsync(destination); |
|||
|
|||
return; |
|||
} |
|||
|
|||
var w = options.Width ?? 0; |
|||
var h = options.Height ?? 0; |
|||
|
|||
await maxTasks.WaitAsync(); |
|||
try |
|||
{ |
|||
using (var image = Image.Load(source, out var format)) |
|||
{ |
|||
var encoder = GetEncoder(options, format); |
|||
|
|||
image.Mutate(x => x.AutoOrient()); |
|||
|
|||
if (w > 0 || h > 0) |
|||
{ |
|||
var isCropUpsize = options.Mode == ResizeMode.CropUpsize; |
|||
|
|||
if (!Enum.TryParse<ISResizeMode>(options.Mode.ToString(), true, out var resizeMode)) |
|||
{ |
|||
resizeMode = ISResizeMode.Max; |
|||
} |
|||
|
|||
if (isCropUpsize) |
|||
{ |
|||
resizeMode = ISResizeMode.Crop; |
|||
} |
|||
|
|||
if (w >= image.Width && h >= image.Height && resizeMode == ISResizeMode.Crop && !isCropUpsize) |
|||
{ |
|||
resizeMode = ISResizeMode.BoxPad; |
|||
} |
|||
|
|||
var resizeOptions = new ISResizeOptions { Size = new Size(w, h), Mode = resizeMode }; |
|||
|
|||
if (options.FocusX.HasValue && options.FocusY.HasValue) |
|||
{ |
|||
resizeOptions.CenterCoordinates = new PointF( |
|||
+(options.FocusX.Value / 2f) + 0.5f, |
|||
-(options.FocusY.Value / 2f) + 0.5f |
|||
); |
|||
} |
|||
|
|||
image.Mutate(x => x.Resize(resizeOptions)); |
|||
} |
|||
|
|||
await image.SaveAsync(destination, encoder); |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
maxTasks.Release(); |
|||
} |
|||
} |
|||
|
|||
private static IImageEncoder GetEncoder(ResizeOptions options, IImageFormat? format) |
|||
{ |
|||
var encoder = Configuration.Default.ImageFormatsManager.FindEncoder(format); |
|||
|
|||
if (encoder == null) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
if (options.Quality.HasValue && (encoder is JpegEncoder || !options.KeepFormat) && options.Format == ImageFormat.Auto) |
|||
{ |
|||
encoder = new JpegEncoder { Quality = options.Quality.Value }; |
|||
} |
|||
else if (options.Format == ImageFormat.JPEG) |
|||
{ |
|||
encoder = new JpegEncoder(); |
|||
} |
|||
else if (options.Format == ImageFormat.PNG) |
|||
{ |
|||
encoder = new PngEncoder(); |
|||
} |
|||
else if (options.Format == ImageFormat.TGA) |
|||
{ |
|||
encoder = new TgaEncoder(); |
|||
} |
|||
else if (options.Format == ImageFormat.GIF) |
|||
{ |
|||
encoder = new GifEncoder(); |
|||
} |
|||
|
|||
return encoder; |
|||
} |
|||
|
|||
public Task<ImageInfo?> GetImageInfoAsync(Stream source) |
|||
{ |
|||
Guard.NotNull(source, nameof(source)); |
|||
|
|||
ImageInfo? result = null; |
|||
|
|||
try |
|||
{ |
|||
var image = Image.Identify(source, out var format); |
|||
|
|||
if (image != null) |
|||
{ |
|||
result = GetImageInfo(image); |
|||
|
|||
result.Format = format.Name; |
|||
} |
|||
} |
|||
catch |
|||
{ |
|||
result = null; |
|||
} |
|||
|
|||
return Task.FromResult(result); |
|||
} |
|||
|
|||
public async Task<ImageInfo> FixOrientationAsync(Stream source, Stream destination) |
|||
{ |
|||
Guard.NotNull(source, nameof(source)); |
|||
Guard.NotNull(destination, nameof(destination)); |
|||
|
|||
await maxTasks.WaitAsync(); |
|||
try |
|||
{ |
|||
using (var image = Image.Load(source, out var format)) |
|||
{ |
|||
var encoder = Configuration.Default.ImageFormatsManager.FindEncoder(format); |
|||
|
|||
if (encoder == null) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
image.Mutate(x => x.AutoOrient()); |
|||
|
|||
await image.SaveAsync(destination, encoder); |
|||
|
|||
return GetImageInfo(image); |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
maxTasks.Release(); |
|||
} |
|||
} |
|||
|
|||
private static ImageInfo GetImageInfo(IImageInfo image) |
|||
{ |
|||
var isRotatedOrSwapped = false; |
|||
|
|||
if (image.Metadata.ExifProfile != null) |
|||
{ |
|||
var value = image.Metadata.ExifProfile.GetValue(ExifTag.Orientation); |
|||
|
|||
isRotatedOrSwapped = value?.Value > 1; |
|||
} |
|||
|
|||
return new ImageInfo(image.Width, image.Height, isRotatedOrSwapped); |
|||
} |
|||
} |
|||
} |
|||
@ -1,128 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Concurrent; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public class MemoryAssetStore : IAssetStore |
|||
{ |
|||
private readonly ConcurrentDictionary<string, MemoryStream> streams = new ConcurrentDictionary<string, MemoryStream>(); |
|||
private readonly AsyncLock readerLock = new AsyncLock(); |
|||
private readonly AsyncLock writerLock = new AsyncLock(); |
|||
|
|||
public string? GeneratePublicUrl(string fileName) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
public async Task<long> GetSizeAsync(string fileName, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
|
|||
if (!streams.TryGetValue(fileName, out var sourceStream)) |
|||
{ |
|||
throw new AssetNotFoundException(fileName); |
|||
} |
|||
|
|||
using (await readerLock.LockAsync()) |
|||
{ |
|||
return sourceStream.Length; |
|||
} |
|||
} |
|||
|
|||
public virtual async Task CopyAsync(string sourceFileName, string targetFileName, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(sourceFileName, nameof(sourceFileName)); |
|||
Guard.NotNullOrEmpty(targetFileName, nameof(targetFileName)); |
|||
|
|||
if (!streams.TryGetValue(sourceFileName, out var sourceStream)) |
|||
{ |
|||
throw new AssetNotFoundException(sourceFileName); |
|||
} |
|||
|
|||
using (await readerLock.LockAsync()) |
|||
{ |
|||
await UploadAsync(targetFileName, sourceStream, false, ct); |
|||
} |
|||
} |
|||
|
|||
public virtual async Task DownloadAsync(string fileName, Stream stream, BytesRange range = default, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
if (!streams.TryGetValue(fileName, out var sourceStream)) |
|||
{ |
|||
throw new AssetNotFoundException(fileName); |
|||
} |
|||
|
|||
using (await readerLock.LockAsync()) |
|||
{ |
|||
try |
|||
{ |
|||
await sourceStream.CopyToAsync(stream, range, ct); |
|||
} |
|||
finally |
|||
{ |
|||
sourceStream.Position = 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public virtual async Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
var memoryStream = new MemoryStream(); |
|||
|
|||
async Task CopyAsync() |
|||
{ |
|||
using (await writerLock.LockAsync()) |
|||
{ |
|||
try |
|||
{ |
|||
await stream.CopyToAsync(memoryStream, 81920, ct); |
|||
} |
|||
finally |
|||
{ |
|||
memoryStream.Position = 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (overwrite) |
|||
{ |
|||
await CopyAsync(); |
|||
|
|||
streams[fileName] = memoryStream; |
|||
} |
|||
else if (streams.TryAdd(fileName, memoryStream)) |
|||
{ |
|||
await CopyAsync(); |
|||
} |
|||
else |
|||
{ |
|||
throw new AssetAlreadyExistsException(fileName); |
|||
} |
|||
} |
|||
|
|||
public virtual Task DeleteAsync(string fileName) |
|||
{ |
|||
Guard.NotNullOrEmpty(fileName, nameof(fileName)); |
|||
|
|||
streams.TryRemove(fileName, out _); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
} |
|||
@ -1,47 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public sealed class NoopAssetStore : IAssetStore |
|||
{ |
|||
public string? GeneratePublicUrl(string fileName) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
public Task<long> GetSizeAsync(string fileName, CancellationToken ct = default) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public Task CopyAsync(string sourceFileName, string fileName, CancellationToken ct = default) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public Task DownloadAsync(string fileName, Stream stream, BytesRange range = default, CancellationToken ct = default) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public Task UploadAsync(string fileName, Stream stream, bool overwrite = false, CancellationToken ct = default) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public Task DeleteAsync(string fileName) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,20 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public enum ResizeMode |
|||
{ |
|||
Crop, |
|||
CropUpsize, |
|||
Pad, |
|||
BoxPad, |
|||
Max, |
|||
Min, |
|||
Stretch |
|||
} |
|||
} |
|||
@ -1,72 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Text; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public sealed class ResizeOptions |
|||
{ |
|||
public ImageFormat Format { get; set; } |
|||
|
|||
public ResizeMode Mode { get; set; } |
|||
|
|||
public int? Width { get; set; } |
|||
|
|||
public int? Height { get; set; } |
|||
|
|||
public int? Quality { get; set; } |
|||
|
|||
public float? FocusX { get; set; } |
|||
|
|||
public float? FocusY { get; set; } |
|||
|
|||
public bool KeepFormat { get; set; } |
|||
|
|||
public bool IsValid |
|||
{ |
|||
get { return Width > 0 || Height > 0 || Quality > 0 || Format != ImageFormat.Auto; } |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
var sb = new StringBuilder(); |
|||
|
|||
sb.Append(Width); |
|||
sb.Append("_"); |
|||
sb.Append(Height); |
|||
sb.Append("_"); |
|||
sb.Append(Mode); |
|||
|
|||
if (Quality.HasValue) |
|||
{ |
|||
sb.Append("_"); |
|||
sb.Append(Quality); |
|||
} |
|||
|
|||
if (FocusX.HasValue) |
|||
{ |
|||
sb.Append("_focusX_"); |
|||
sb.Append(FocusX); |
|||
} |
|||
|
|||
if (FocusY.HasValue) |
|||
{ |
|||
sb.Append("_focusY_"); |
|||
sb.Append(FocusY); |
|||
} |
|||
|
|||
if (Format != ImageFormat.Auto) |
|||
{ |
|||
sb.Append("_format_"); |
|||
sb.Append(Format.ToString()); |
|||
} |
|||
|
|||
return sb.ToString(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,64 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.IO; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Infrastructure.Assets |
|||
{ |
|||
public static class StreamExtensions |
|||
{ |
|||
private static readonly ArrayPool<byte> Pool = ArrayPool<byte>.Create(); |
|||
|
|||
public static async Task CopyToAsync(this Stream source, Stream target, BytesRange range, CancellationToken ct, bool skip = true) |
|||
{ |
|||
var buffer = Pool.Rent(8192); |
|||
|
|||
try |
|||
{ |
|||
if (skip && range.From > 0) |
|||
{ |
|||
source.Seek(range.From.Value, SeekOrigin.Begin); |
|||
} |
|||
|
|||
var bytesLeft = range.Length; |
|||
|
|||
while (true) |
|||
{ |
|||
if (bytesLeft <= 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
ct.ThrowIfCancellationRequested(); |
|||
|
|||
var readLength = (int)Math.Min(buffer.Length, bytesLeft); |
|||
|
|||
var read = await source.ReadAsync(buffer, 0, readLength, ct); |
|||
|
|||
bytesLeft -= read; |
|||
|
|||
if (read == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
ct.ThrowIfCancellationRequested(); |
|||
|
|||
await target.WriteAsync(buffer, 0, read, ct); |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
Pool.Return(buffer); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,65 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Infrastructure |
|||
{ |
|||
public readonly struct BytesRange |
|||
{ |
|||
public readonly long? From; |
|||
|
|||
public readonly long? To; |
|||
|
|||
public long Length |
|||
{ |
|||
get |
|||
{ |
|||
if (To < 0 || From < 0) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
var result = (To ?? long.MaxValue) - (From ?? 0); |
|||
|
|||
if (result == long.MaxValue) |
|||
{ |
|||
return long.MaxValue; |
|||
} |
|||
|
|||
return Math.Max(0, result + 1); |
|||
} |
|||
} |
|||
|
|||
public bool IsDefined |
|||
{ |
|||
get { return (From >= 0 || To >= 0) && Length > 0; } |
|||
} |
|||
|
|||
public BytesRange(long? from, long? to) |
|||
{ |
|||
From = from; |
|||
|
|||
To = to; |
|||
} |
|||
|
|||
public override string? ToString() |
|||
{ |
|||
if (Length == 0) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
if (From.HasValue || To.HasValue) |
|||
{ |
|||
return $"bytes={From}-{To}"; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue