diff --git a/PowerKit.Tests/FileExtensionsTests.cs b/PowerKit.Tests/FileExtensionsTests.cs new file mode 100644 index 0000000..cbd3301 --- /dev/null +++ b/PowerKit.Tests/FileExtensionsTests.cs @@ -0,0 +1,134 @@ +using System.IO; +using System.Threading.Tasks; +using FluentAssertions; +using PowerKit; +using PowerKit.Extensions; +using Xunit; + +namespace PowerKit.Tests; + +public class FileExtensionsTests +{ + [Fact] + public async Task WriteAllZeroes_Test() + { + // Arrange + using var tempFile = TempFile.Create(); + + // Act + File.WriteAllZeroes(tempFile.Path, 1024); + + // Assert + var bytes = await File.ReadAllBytesAsync(tempFile.Path); + bytes.Should().HaveCount(1024); + bytes.Should().AllSatisfy(b => b.Should().Be(0)); + } + + [Fact] + public void ReadAllBytes_WithOffset_Test() + { + // Arrange + using var tempFile = TempFile.Create(); + File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]); + + // Act + var bytes = File.ReadAllBytes(tempFile.Path, offset: 2L); + + // Assert + bytes.Should().Equal(3, 4, 5); + } + + [Fact] + public void ReadAllBytes_WithOffset_AtEndOfFile_Test() + { + // Arrange + using var tempFile = TempFile.Create(); + File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]); + + // Act + var bytes = File.ReadAllBytes(tempFile.Path, offset: 5L); + + // Assert + bytes.Should().BeEmpty(); + } + + [Fact] + public void ReadAllBytes_WithOffset_PastEndOfFile_Test() + { + // Arrange + using var tempFile = TempFile.Create(); + File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]); + + // Act & Assert + var bytes = File.ReadAllBytes(tempFile.Path, offset: 10L); + bytes.Should().BeEmpty(); + } + + [Fact] + public void ReadAllBytes_WithOffsetAndLength_Test() + { + // Arrange + using var tempFile = TempFile.Create(); + File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]); + + // Act + var bytes = File.ReadAllBytes(tempFile.Path, offset: 1L, length: 3); + + // Assert + bytes.Should().Equal(2, 3, 4); + } + + [Fact] + public async Task ReadAllBytesAsync_WithOffset_Test() + { + // Arrange + using var tempFile = TempFile.Create(); + File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]); + + // Act + var bytes = await File.ReadAllBytesAsync(tempFile.Path, offset: 2L); + + // Assert + bytes.Should().Equal(3, 4, 5); + } + + [Fact] + public async Task ReadAllBytesAsync_WithOffset_AtEndOfFile_Test() + { + // Arrange + using var tempFile = TempFile.Create(); + File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]); + + // Act + var bytes = await File.ReadAllBytesAsync(tempFile.Path, offset: 5L); + + // Assert + bytes.Should().BeEmpty(); + } + + [Fact] + public async Task ReadAllBytesAsync_WithOffset_PastEndOfFile_Test() + { + // Arrange + using var tempFile = TempFile.Create(); + File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]); + + // Act & Assert + var bytes = await File.ReadAllBytesAsync(tempFile.Path, offset: 10L); + bytes.Should().BeEmpty(); + } + + [Fact] + public async Task ReadAllBytesAsync_WithOffsetAndLength_Test() + { + // Arrange + using var tempFile = TempFile.Create(); + File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]); + + // Act + var bytes = await File.ReadAllBytesAsync(tempFile.Path, offset: 1L, length: 3); + + // Assert + bytes.Should().Equal(2, 3, 4); + } +} diff --git a/PowerKit/Extensions/FileExtensions.cs b/PowerKit/Extensions/FileExtensions.cs new file mode 100644 index 0000000..a696055 --- /dev/null +++ b/PowerKit/Extensions/FileExtensions.cs @@ -0,0 +1,140 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace PowerKit.Extensions; + +internal static class FileExtensions +{ + extension(File) + { + /// + /// Creates a file at the specified path and fills it with zeroes. + /// + public static void WriteAllZeroes(string path, long count) + { + using var stream = new FileStream( + path, + FileMode.Create, + FileAccess.Write, + FileShare.None + ); + + stream.SetLength(count); + } + + /// + /// Reads all bytes from the specified file starting at the given offset. + /// + public static byte[] ReadAllBytes(string path, long offset) + { + using var stream = new FileStream( + path, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite + ); + + stream.Seek(offset, SeekOrigin.Begin); + + if (offset >= stream.Length) + { + return []; + } + + var buffer = new byte[checked((int)(stream.Length - offset))]; + stream.ReadExactly(buffer); + + return buffer; + } + + /// + /// Reads the specified number of bytes from the file starting at the given offset. + /// + public static byte[] ReadAllBytes(string path, long offset, int length) + { + using var stream = new FileStream( + path, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite + ); + + stream.Seek(offset, SeekOrigin.Begin); + + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var buffer = new byte[length]; + stream.ReadExactly(buffer); + + return buffer; + } + + /// + /// Reads all bytes from the specified file starting at the given offset asynchronously. + /// + public static async Task ReadAllBytesAsync( + string path, + long offset, + CancellationToken cancellationToken = default + ) + { + using var stream = new FileStream( + path, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite, + bufferSize: 4096, + FileOptions.Asynchronous + ); + + stream.Seek(offset, SeekOrigin.Begin); + + if (offset >= stream.Length) + { + return []; + } + + var buffer = new byte[checked((int)(stream.Length - offset))]; + await stream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false); + + return buffer; + } + + /// + /// Reads the specified number of bytes from the file starting at the given offset asynchronously. + /// + public static async Task ReadAllBytesAsync( + string path, + long offset, + int length, + CancellationToken cancellationToken = default + ) + { + using var stream = new FileStream( + path, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite, + bufferSize: 4096, + FileOptions.Asynchronous + ); + + stream.Seek(offset, SeekOrigin.Begin); + + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var buffer = new byte[length]; + await stream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false); + + return buffer; + } + } +}