mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-02 05:48:47 +01:00
Moved IsFileIdenticalAsync & IsStreamIdenticalAsync to StreamExtensions.
This commit is contained in:
@@ -208,7 +208,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||||||
Directory.CreateDirectory(directory);
|
Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
// Compare byte-for-byte before proceeding.
|
// 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.
|
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)
|
private void SetHidden(string path, bool hidden)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Jellyfin.Extensions
|
namespace Jellyfin.Extensions
|
||||||
{
|
{
|
||||||
@@ -12,6 +15,8 @@ namespace Jellyfin.Extensions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class StreamExtensions
|
public static class StreamExtensions
|
||||||
{
|
{
|
||||||
|
private const int StreamComparisonBufferSize = 65536;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads all lines in the <see cref="Stream" />.
|
/// Reads all lines in the <see cref="Stream" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -60,5 +65,96 @@ namespace Jellyfin.Extensions
|
|||||||
yield return line;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user