diff options
| author | Marc Brooks <IDisposable@gmail.com> | 2026-05-26 22:37:17 +0000 |
|---|---|---|
| committer | Marc Brooks <IDisposable@gmail.com> | 2026-05-26 18:12:31 -0500 |
| commit | 02ca63cd13779dbff9971e10a7afd62d2634337b (patch) | |
| tree | 554ca1f5cd1a5e005d7ad9577da607be786258e7 | |
| parent | 961c6d3d547a1e3c6352129b45fffd1158571d15 (diff) | |
Moved IsFileIdenticalAsync & IsStreamIdenticalAsync to StreamExtensions.
| -rw-r--r-- | MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs | 64 | ||||
| -rw-r--r-- | src/Jellyfin.Extensions/StreamExtensions.cs | 96 |
2 files changed, 97 insertions, 63 deletions
diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 64daac68e3..78907a5e68 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -208,7 +208,7 @@ namespace MediaBrowser.XbmcMetadata.Savers Directory.CreateDirectory(directory); // Compare byte-for-byte before proceeding. - if (File.Exists(path) && await IsFileIdenticalAsync(stream, path, cancellationToken).ConfigureAwait(false)) + if (File.Exists(path) && await stream.IsFileIdenticalAsync(path, cancellationToken).ConfigureAwait(false)) { return; // Don't save since .nfo is unchanged. } @@ -239,68 +239,6 @@ namespace MediaBrowser.XbmcMetadata.Savers } } - private static async Task<bool> IsFileIdenticalAsync(Stream stream, string path, CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(stream); - ArgumentException.ThrowIfNullOrEmpty(path); - - if (!stream.CanSeek) - { - return false; - } - - const int BufferSize = 81920; - var originalPosition = stream.Position; - - try - { - stream.Position = 0; - - using var existingFileStream = new FileStream( - path, - FileMode.Open, - FileAccess.Read, - FileShare.Read, - bufferSize: BufferSize, - FileOptions.Asynchronous); - - if (existingFileStream.Length != stream.Length) - { - return false; - } - - var streamBuffer = new byte[BufferSize]; - var existingBuffer = new byte[BufferSize]; - - while (true) - { - cancellationToken.ThrowIfCancellationRequested(); - - var streamBytesRead = await stream.ReadAsync(streamBuffer.AsMemory(), cancellationToken).ConfigureAwait(false); - var existingBytesRead = await existingFileStream.ReadAsync(existingBuffer.AsMemory(), cancellationToken).ConfigureAwait(false); - - if (streamBytesRead != existingBytesRead) - { - return false; - } - - if (streamBytesRead == 0) - { - return true; - } - - if (!streamBuffer.AsSpan(0, streamBytesRead).SequenceEqual(existingBuffer.AsSpan(0, existingBytesRead))) - { - return false; - } - } - } - finally - { - stream.Position = originalPosition; - } - } - private void SetHidden(string path, bool hidden) { try 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); + } + } } } |
