|
1 | 1 | // Copyright (c) Event Store Ltd and/or licensed to Event Store Ltd under one or more agreements.
|
2 | 2 | // Event Store Ltd licenses this file to you under the Event Store License v2 (see LICENSE.md).
|
3 | 3 |
|
4 |
| -using System; |
5 |
| -using System.IO; |
| 4 | +using System.Buffers; |
| 5 | +using System.Diagnostics; |
| 6 | +using System.Runtime.CompilerServices; |
6 | 7 | using System.Security.Cryptography;
|
7 | 8 |
|
8 | 9 | namespace EventStore.Plugins.Transforms;
|
9 | 10 |
|
10 |
| -public class ChunkDataWriteStream(Stream chunkFileStream, HashAlgorithm checksumAlgorithm) : Stream { |
11 |
| - public Stream ChunkFileStream => chunkFileStream; |
12 |
| - public HashAlgorithm ChecksumAlgorithm => checksumAlgorithm; |
| 11 | +public class ChunkDataWriteStream(Stream chunkFileStream, IncrementalHash checksumAlgorithm) : ChunkDataStream(chunkFileStream) { |
| 12 | + private long? _positionToHash; |
13 | 13 |
|
14 | 14 | public sealed override bool CanRead => false;
|
15 | 15 | public sealed override bool CanSeek => false;
|
16 | 16 | public sealed override bool CanWrite => true;
|
17 |
| - public sealed override int Read(byte[] buffer, int offset, int count) => throw new InvalidOperationException(); |
| 17 | + |
| 18 | + public sealed override int Read(Span<byte> buffer) => throw new NotSupportedException(); |
| 19 | + |
| 20 | + public override void Write(ReadOnlySpan<byte> buffer) => ChunkFileStream.Write(buffer); |
| 21 | + |
| 22 | + public override void Flush() => ChunkFileStream.Flush(); |
| 23 | + |
18 | 24 | public sealed override long Seek(long offset, SeekOrigin origin) => throw new InvalidOperationException();
|
19 | 25 |
|
20 |
| - public override void Write(byte[] buffer, int offset, int count) { |
21 |
| - ChunkFileStream.Write(buffer, offset, count); |
22 |
| - ChecksumAlgorithm.TransformBlock(buffer, 0, count, null, 0); |
| 26 | + public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default) |
| 27 | + => _positionToHash is { } count ? WriteAndChecksumAsync(count, buffer, token) : WriteWithoutChecksumAsync(buffer, token); |
| 28 | + |
| 29 | + [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))] |
| 30 | + private async ValueTask WriteWithoutChecksumAsync(ReadOnlyMemory<byte> buffer, CancellationToken token) { |
| 31 | + await ChunkFileStream.WriteAsync(buffer, token); |
| 32 | + checksumAlgorithm.AppendData(buffer.Span); |
23 | 33 | }
|
24 | 34 |
|
25 |
| - public override void Flush() => ChunkFileStream.Flush(); |
| 35 | + private async ValueTask WriteAndChecksumAsync(long count, ReadOnlyMemory<byte> buffer, CancellationToken token) { |
| 36 | + await ReadAndChecksumAsync(count, token); |
| 37 | + |
| 38 | + Debug.Assert(ChunkFileStream.Position == count); |
| 39 | + await ChunkFileStream.WriteAsync(buffer, token); |
| 40 | + checksumAlgorithm.AppendData(buffer.Span); |
| 41 | + _positionToHash = null; |
| 42 | + } |
| 43 | + |
| 44 | + public override Task FlushAsync(CancellationToken ct) => ChunkFileStream.FlushAsync(ct); |
26 | 45 | public override void SetLength(long value) => ChunkFileStream.SetLength(value);
|
27 | 46 | public override long Length => ChunkFileStream.Length;
|
28 | 47 | public override long Position {
|
29 |
| - get => ChunkFileStream.Position; |
| 48 | + get => _positionToHash ?? ChunkFileStream.Position; |
30 | 49 | set {
|
31 |
| - if (ChunkFileStream.Position != 0) |
| 50 | + if (ChunkFileStream.Position is not 0L) |
32 | 51 | throw new InvalidOperationException("Writer's position can only be moved from 0 to a higher value.");
|
33 | 52 |
|
34 |
| - ReadAndChecksum(value); |
35 |
| - |
36 |
| - if (ChunkFileStream.Position != value) |
37 |
| - throw new Exception($"Writer's position ({ChunkFileStream.Position:N0}) is not at the expected position ({value:N0})"); |
| 53 | + if (value is not 0L) |
| 54 | + _positionToHash = value; |
38 | 55 | }
|
39 | 56 | }
|
40 | 57 |
|
41 |
| - private void ReadAndChecksum(long count) { |
42 |
| - var buffer = new byte[4096]; |
43 |
| - long toRead = count; |
44 |
| - while (toRead > 0) { |
45 |
| - int read = ChunkFileStream.Read(buffer, 0, (int)Math.Min(toRead, buffer.Length)); |
46 |
| - if (read == 0) |
47 |
| - break; |
48 |
| - |
49 |
| - ChecksumAlgorithm.TransformBlock(buffer, 0, read, null, 0); |
50 |
| - toRead -= read; |
51 |
| - } |
52 |
| - } |
| 58 | + private async ValueTask ReadAndChecksumAsync(long count, CancellationToken token) { |
| 59 | + var buffer = ArrayPool<byte>.Shared.Rent(4096); |
53 | 60 |
|
54 |
| - protected override void Dispose(bool disposing) { |
55 | 61 | try {
|
56 |
| - if (!disposing) |
57 |
| - return; |
| 62 | + for (int bytesRead; count > 0L; count -= bytesRead) { |
| 63 | + bytesRead = await ChunkFileStream.ReadAsync(buffer.AsMemory(0, (int)long.Min(count, buffer.Length)), |
| 64 | + token); |
| 65 | + if (bytesRead is 0) |
| 66 | + break; |
58 | 67 |
|
59 |
| - chunkFileStream.Dispose(); |
| 68 | + checksumAlgorithm.AppendData(new ReadOnlySpan<byte>(buffer, 0, bytesRead)); |
| 69 | + } |
60 | 70 | } finally {
|
61 |
| - base.Dispose(disposing); |
| 71 | + ArrayPool<byte>.Shared.Return(buffer); |
62 | 72 | }
|
63 | 73 | }
|
64 | 74 | }
|
0 commit comments