diff options
Diffstat (limited to 'src/Jellyfin.Extensions/StreamExtensions.cs')
| -rw-r--r-- | src/Jellyfin.Extensions/StreamExtensions.cs | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/src/Jellyfin.Extensions/StreamExtensions.cs b/src/Jellyfin.Extensions/StreamExtensions.cs index 0cfac384e3..fa019b0059 100644 --- a/src/Jellyfin.Extensions/StreamExtensions.cs +++ b/src/Jellyfin.Extensions/StreamExtensions.cs @@ -1,9 +1,12 @@ +using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading; +using System.Threading.Tasks; namespace Jellyfin.Extensions { @@ -12,6 +15,8 @@ namespace Jellyfin.Extensions /// </summary> public static class StreamExtensions { + private const int StreamComparisonBufferSize = 65536; + /// <summary> /// Reads all lines in the <see cref="Stream" />. /// </summary> @@ -60,5 +65,96 @@ namespace Jellyfin.Extensions yield return line; } } + + /// <summary> + /// Determines whether a stream is identical to a file on disk. + /// </summary> + /// <param name="stream">The stream to compare.</param> + /// <param name="path">The file path to compare against.</param> + /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> + /// <returns>True if the stream and file are identical; otherwise false.</returns> + public static async Task<bool> IsFileIdenticalAsync(this Stream stream, string path, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(stream); + ArgumentException.ThrowIfNullOrEmpty(path); + + if (!stream.CanSeek) + { + return false; + } + + var originalPosition = stream.Position; + try + { + stream.Position = 0; + + var existingFileStream = new FileStream( + path, + FileMode.Open, + FileAccess.Read, + FileShare.Read, + bufferSize: StreamComparisonBufferSize, + FileOptions.Asynchronous | FileOptions.SequentialScan); + await using (existingFileStream.ConfigureAwait(false)) + { + return await stream.IsStreamIdenticalAsync(existingFileStream, cancellationToken).ConfigureAwait(false); + } + } + finally + { + stream.Position = originalPosition; + } + } + + /// <summary> + /// Determines whether two streams are identical. + /// </summary> + /// <param name="a">The first stream to compare.</param> + /// <param name="b">The second stream to compare.</param> + /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> + /// <returns>True if the streams are identical; otherwise false.</returns> + public static async Task<bool> IsStreamIdenticalAsync(this Stream a, Stream b, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(a); + ArgumentNullException.ThrowIfNull(b); + + if (b.Length != a.Length) + { + return false; + } + + var bufferA = ArrayPool<byte>.Shared.Rent(StreamComparisonBufferSize); + var bufferB = ArrayPool<byte>.Shared.Rent(StreamComparisonBufferSize); + try + { + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + + var bytesReadA = await a.ReadAsync(bufferA.AsMemory(), cancellationToken).ConfigureAwait(false); + var bytesReadB = await b.ReadAsync(bufferB.AsMemory(), cancellationToken).ConfigureAwait(false); + + if (bytesReadA != bytesReadB) + { + return false; + } + + if (bytesReadA == 0) + { + return true; + } + + if (!bufferA.AsSpan(0, bytesReadA).SequenceEqual(bufferB.AsSpan(0, bytesReadB))) + { + return false; + } + } + } + finally + { + ArrayPool<byte>.Shared.Return(bufferA); + ArrayPool<byte>.Shared.Return(bufferB); + } + } } } |
