mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
34 changed files with 420 additions and 96 deletions
@ -0,0 +1,64 @@ |
|||
// ==========================================================================
|
|||
// 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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Infrastructure |
|||
{ |
|||
public 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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure |
|||
{ |
|||
public class BytesRangeTests |
|||
{ |
|||
[Fact] |
|||
public void Should_create_default() |
|||
{ |
|||
var sut = (BytesRange)default; |
|||
|
|||
TestBytesRange(sut, null, null, long.MaxValue, false, null); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_create_default_manually() |
|||
{ |
|||
var sut = new BytesRange(null, null); |
|||
|
|||
TestBytesRange(sut, null, null, long.MaxValue, false, null); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_create_with_from() |
|||
{ |
|||
var sut = new BytesRange(12, null); |
|||
|
|||
TestBytesRange(sut, 12, null, long.MaxValue - 11, true, "bytes=12-"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_create_with_to() |
|||
{ |
|||
var sut = new BytesRange(null, 12); |
|||
|
|||
TestBytesRange(sut, null, 12, 13, true, "bytes=-12"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_create_with_from_and_to() |
|||
{ |
|||
var sut = new BytesRange(3, 15); |
|||
|
|||
TestBytesRange(sut, 3, 15, 13, true, "bytes=3-15"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_create_with_single_byte() |
|||
{ |
|||
var sut = new BytesRange(3, 3); |
|||
|
|||
TestBytesRange(sut, 3, 3, 1, true, "bytes=3-3"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_fix_length() |
|||
{ |
|||
var sut = new BytesRange(5, 3); |
|||
|
|||
TestBytesRange(sut, 5, 3, 0, false, null); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_fix_length_for_negative_range() |
|||
{ |
|||
var sut = new BytesRange(-5, -3); |
|||
|
|||
TestBytesRange(sut, -5, -3, 0, false, null); |
|||
} |
|||
|
|||
private void TestBytesRange(BytesRange sut, long? from, long? to, long length, bool defined, string? formatted) |
|||
{ |
|||
Assert.Equal(from, sut.From); |
|||
Assert.Equal(to, sut.To); |
|||
Assert.Equal(length, sut.Length); |
|||
Assert.Equal(defined, sut.IsDefined); |
|||
Assert.Equal(formatted, sut.ToString()); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue