From b2f97fe071f6aad24dd6fee6e139de9a1b4aeebf Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Tue, 30 Jul 2024 13:49:59 +0100 Subject: [PATCH] Added: Extensions for Remaining High Level Nx APIs --- .../Extensions/NxPackerBuilderExtensions.cs | 76 +++++++++++++++++++ .../Extensions/NxUnpackerBuilderExtensions.cs | 71 +++++++++++++++++ .../Extensions/NxBuilderExtensionsTests.cs | 60 +++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 src/Extensions/NexusMods.Paths.Extensions.Nx/Extensions/NxPackerBuilderExtensions.cs create mode 100644 src/Extensions/NexusMods.Paths.Extensions.Nx/Extensions/NxUnpackerBuilderExtensions.cs create mode 100644 tests/Extensions/NexusMods.Paths.Extensions.Nx.Tests/Extensions/NxBuilderExtensionsTests.cs diff --git a/src/Extensions/NexusMods.Paths.Extensions.Nx/Extensions/NxPackerBuilderExtensions.cs b/src/Extensions/NexusMods.Paths.Extensions.Nx/Extensions/NxPackerBuilderExtensions.cs new file mode 100644 index 0000000..f908a32 --- /dev/null +++ b/src/Extensions/NexusMods.Paths.Extensions.Nx/Extensions/NxPackerBuilderExtensions.cs @@ -0,0 +1,76 @@ +using JetBrains.Annotations; +using NexusMods.Archives.Nx.Enums; +using NexusMods.Archives.Nx.Packing; +using NexusMods.Archives.Nx.Structs; +using NexusMods.Paths.Extensions.Nx.FileProviders; +namespace NexusMods.Paths.Extensions.Nx.Extensions; + +/// +/// Extension methods for NxPackerBuilder to integrate AbsolutePath-based APIs. +/// +[PublicAPI] +public static class NxPackerBuilderExtensions +{ + /// + /// Adds a file to be packed using an . + /// + /// The instance. + /// The of the file to add. + /// The options for adding the file. + /// The builder instance for method chaining. + public static NxPackerBuilder AddFile(this NxPackerBuilder builder, AbsolutePath absolutePath, AddFileParams options) + { + var packerFile = new PackerFile + { + FileDataProvider = new FromAbsolutePathProvider + { + FilePath = absolutePath + }, + RelativePath = options.RelativePath, + FileSize = (long)absolutePath.FileInfo.Size.Value, + SolidType = options.SolidType, + CompressionPreference = options.CompressionPreference + }; + + return builder.AddPackerFile(packerFile); + } + + /// + /// Sets the output (archive) to an . + /// + /// The instance. + /// The where the packed archive will be saved. + /// The builder instance for method chaining. + public static NxPackerBuilder WithOutput(this NxPackerBuilder builder, AbsolutePath outputPath) + { + var stream = outputPath.FileSystem.CreateFile(outputPath); + return builder.WithOutput(stream); + } + + /// + /// Adds all files under a given folder to the output using . + /// + /// The instance. + /// The of the folder to add items from. + /// The solid type preference for the files. + /// The compression preference for the files. + /// The builder instance for method chaining. + public static NxPackerBuilder AddFolder(this NxPackerBuilder builder, + AbsolutePath folderPath, + SolidPreference solidType = SolidPreference.Default, + CompressionPreference compressionPreference = CompressionPreference.NoPreference) + { + foreach (var file in folderPath.EnumerateFiles()) + { + var options = new AddFileParams + { + RelativePath = file.RelativeTo(folderPath).ToString(), + SolidType = solidType, + CompressionPreference = compressionPreference + }; + + builder.AddFile(file, options); + } + return builder; + } +} diff --git a/src/Extensions/NexusMods.Paths.Extensions.Nx/Extensions/NxUnpackerBuilderExtensions.cs b/src/Extensions/NexusMods.Paths.Extensions.Nx/Extensions/NxUnpackerBuilderExtensions.cs new file mode 100644 index 0000000..4849278 --- /dev/null +++ b/src/Extensions/NexusMods.Paths.Extensions.Nx/Extensions/NxUnpackerBuilderExtensions.cs @@ -0,0 +1,71 @@ +using JetBrains.Annotations; +using NexusMods.Archives.Nx.Packing; +using NexusMods.Archives.Nx.Packing.Unpack; +using NexusMods.Paths.Extensions.Nx.FileProviders; +namespace NexusMods.Paths.Extensions.Nx.Extensions; + +/// +/// Extension methods for to integrate -based APIs. +/// +[PublicAPI] +public static class NxUnpackerBuilderExtensions +{ + /// + /// Creates an instance using an AbsolutePath. + /// + /// The of the .nx archive file. + /// Hint whether the archive contains lots of files (100+). + /// A new instance of . + public static NxUnpackerBuilder FromFile(AbsolutePath archivePath, bool hasLotsOfFiles = false) + { + return new NxUnpackerBuilder(new FromAbsolutePathProvider + { + FilePath = archivePath + }, hasLotsOfFiles); + } + + /// + /// Extracts files to a specified directory using . + /// + /// The instance. + /// The of the directory to extract files to. + /// The file entries to extract. + /// The builder instance for method chaining. + public static NxUnpackerBuilder AddFilesWithFileSystemOutput(this NxUnpackerBuilder builder, AbsolutePath outputDirectory, PathedFileEntry[] entries) + { + var outputProviders = new OutputAbsolutePathProvider[entries.Length]; + for (var x = 0; x < entries.Length; x++) + { + var entry = entries[x]; + var outputPath = outputDirectory.Combine(entry.FilePath); + outputProviders[x] = new OutputAbsolutePathProvider(outputPath, entry.FilePath, entry.Entry); + } + builder.Outputs.AddRange(outputProviders); + return builder; + } + + /// + /// Extracts all files to a specified directory using AbsolutePath. + /// + /// The instance. + /// The of the directory to extract files to. + /// The builder instance for method chaining. + public static NxUnpackerBuilder AddAllFilesWithFileSystemOutput(this NxUnpackerBuilder builder, AbsolutePath outputDirectory) + { + return builder.AddFilesWithFileSystemOutput(outputDirectory, builder.GetPathedFileEntries()); + } + + /// + /// Extracts a single file to a specified path using . + /// + /// The instance. + /// The file entry to extract. + /// The where the file should be extracted. + /// The builder instance for method chaining. + public static NxUnpackerBuilder AddFileWithFileSystemOutput(this NxUnpackerBuilder builder, PathedFileEntry entry, AbsolutePath outputPath) + { + // Output provider disposed during extract. + builder.Outputs.Add(new OutputAbsolutePathProvider(outputPath, entry.FilePath, entry.Entry)); + return builder; + } +} diff --git a/tests/Extensions/NexusMods.Paths.Extensions.Nx.Tests/Extensions/NxBuilderExtensionsTests.cs b/tests/Extensions/NexusMods.Paths.Extensions.Nx.Tests/Extensions/NxBuilderExtensionsTests.cs new file mode 100644 index 0000000..8e7e1ea --- /dev/null +++ b/tests/Extensions/NexusMods.Paths.Extensions.Nx.Tests/Extensions/NxBuilderExtensionsTests.cs @@ -0,0 +1,60 @@ +using FluentAssertions; +using NexusMods.Archives.Nx.Packing; +using NexusMods.Paths.Extensions.Nx.Extensions; +using NexusMods.Paths.TestingHelpers; +using Xunit; +namespace NexusMods.Paths.Extensions.Nx.Tests.Extensions; + +/// +/// These tests are more of a 'sanity' check to ensure that our extensions +/// integrate correctly with Nx. They don't have much substance, other than +/// confirming that stuff 'just works'. +/// +public class NxBuilderExtensionsTests +{ + [Theory, AutoFileSystem] + public async Task NxPackerBuilder_CanAddFolderFromIFileSystem_AndExtractToIFileSystem(InMemoryFileSystem fs, AbsolutePath folderPath) + { + // Arrange + var file1 = folderPath.Combine("file1.txt"); + var file2 = folderPath.Combine("subfolder/file2.txt"); + await fs.WriteAllTextAsync(file1, "Content 1"); + await fs.WriteAllTextAsync(file2, "Content 2"); + + var builder = new NxPackerBuilder(); + var outputPath = folderPath.Parent.Combine("output.nx"); + + // Act + builder.AddFolder(folderPath) + .WithOutput(outputPath) + .Build(); + + // Assert + fs.FileExists(outputPath).Should().BeTrue(); + var unpacker = NxUnpackerBuilderExtensions.FromFile(outputPath); + var entries = unpacker.GetPathedFileEntries(); + entries.Should().HaveCount(2); + entries.Should().Contain(e => e.FilePath == "file1.txt"); + entries.Should().Contain(e => e.FilePath == "subfolder/file2.txt"); + + // Verify we can extract all files + var extractFolder = folderPath.Parent.Combine("extracted"); + unpacker.AddAllFilesWithFileSystemOutput(extractFolder).Extract(); + + var extractedFile1 = extractFolder.Combine("file1.txt"); + var extractedFile2 = extractFolder.Combine("subfolder/file2.txt"); + + fs.FileExists(extractedFile1).Should().BeTrue(); + fs.FileExists(extractedFile2).Should().BeTrue(); + + (await fs.ReadAllTextAsync(extractedFile1)).Should().Be("Content 1"); + (await fs.ReadAllTextAsync(extractedFile2)).Should().Be("Content 2"); + + // Verify we can extract a single file. + unpacker = NxUnpackerBuilderExtensions.FromFile(outputPath); + var extractedFile1Copy = extractFolder.Combine("file1-copy.txt"); + var file1Entry = entries.First(x => x.FilePath == "file1.txt"); + unpacker.AddFileWithFileSystemOutput(file1Entry, extractedFile1Copy).Extract(); + (await fs.ReadAllTextAsync(extractedFile1Copy)).Should().Be("Content 1"); + } +}