Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZipExtensionWrapper class added with unit tests. #693

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions DataAccess/Extensions/ZipExtensionWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.IO.Abstractions;
using System.IO.Compression;

namespace DataAccess.Extensions;
/// <inheritdoc cref="ZipExtension"/>
public class ZipExtensionWrapper
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Da die Klasse, auf die sich der Wrapper bezieht ZipExtensions heißt, sollte auch der Wrapper ZipExtensionsWrapper heißen.

{
/// <summary>
/// Gets the file system used by this wrapper.
/// </summary>
public IFileSystem FileSystem { get; private set; }


public ZipExtensionWrapper(IFileSystem fileSystem)
{
FileSystem = fileSystem;
}

/// <inheritdoc cref="ZipExtension.GetZipArchive"/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Die doc kann nicht einfach so übernommen werden, da es den Parameter für das FileSystem im Wrapper nicht mehr gibt.

public ZipArchive GetZipArchive(string archivePath)
{
return ZipExtensions.GetZipArchive(FileSystem, archivePath);
}

/// <inheritdoc cref="ZipExtension.ExtractToDirectory"/>
public void ExtractToDirectory(ZipArchive archive, string destination)
{
ZipExtensions.ExtractToDirectory(archive, FileSystem, destination);
}

/// <inheritdoc cref="ZipExtension.CreateFromDirectoryAsync"/>
public async Task CreateFromDirectoryAsync(string sourcePath, string destinationPath)
{
await ZipExtensions.CreateFromDirectoryAsync(FileSystem, sourcePath, destinationPath);
}
}
140 changes: 96 additions & 44 deletions DataAccess/Extensions/ZipExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,74 +6,126 @@ namespace DataAccess.Extensions;
public static class ZipExtensions
{
/// <summary>
/// Creates a <see cref="ZipArchive"/> object for a given path on the filesystem <paramref name="fs"/>.
/// Opens a zip archive in read mode.
/// </summary>
/// <param name="fs">The filesystem on which the file resides.</param>
/// <param name="archivePath">The path to the zip archive on the filesystem.</param>
/// <returns></returns>
public static ZipArchive GetZipArchive(IFileSystem fs, string archivePath)
/// <param name="fileSystemReference">The file system abstraction used to access the file.</param>
/// <param name="archivePath">The path to the zip archive file.</param>
/// <returns>A <see cref="ZipArchive"/> instance representing the archive.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="fileSystemReference"/> or <paramref name="archivePath"/> is null.</exception>
/// <exception cref="FileNotFoundException">Thrown if the specified file does not exist.</exception>
/// <exception cref="UnauthorizedAccessException">Thrown if the caller does not have the required permissions.</exception>
/// <exception cref="InvalidDataException">Thrown if the file is not a valid zip archive.</exception>
public static ZipArchive GetZipArchive(IFileSystem fileSystemReference, string archivePath)
{
var zipStream = fs.File.OpenRead(archivePath);
var zipStream = fileSystemReference.File.OpenRead(archivePath);
return new ZipArchive(zipStream, ZipArchiveMode.Read);
}

private static ZipArchive GetWritableZipArchive(IFileSystem fs, string archivePath)
private static ZipArchive GetWritableZipArchive(IFileSystem fileSystemReference, string archivePath)
{
var zipStream = fs.File.OpenWrite(archivePath);
var zipStream = fileSystemReference.File.OpenWrite(archivePath);
return new ZipArchive(zipStream, ZipArchiveMode.Create);
}

/// <summary>
/// Extracts the contents of a <see cref="ZipArchive"/> to a given directory on the filesystem <paramref name="fs"/>.
/// Extracts all entries from a zip archive to the specified directory.
/// </summary>
/// <param name="archive">The zip archive to extract.</param>
/// <param name="fs">The filesystem to extract the archive onto.</param>
/// <param name="destination">The destination path on the filesystem.</param>
public static void ExtractToDirectory(this ZipArchive archive, IFileSystem fs, string destination)
/// <param name="fileSystemReference">The file system abstraction used to access files and directories.</param>
/// <param name="destination">The directory to extract the archive's contents to.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="destination"/> is null.</exception>
/// <exception cref="UnauthorizedAccessException">Thrown if the caller does not have write access to the destination directory.</exception>
/// <exception cref="IOException">Thrown if an I/O error occurs during extraction.</exception>
public static void ExtractToDirectory(this ZipArchive archive, IFileSystem fileSystemReference, string destination)
{
if (!fs.Directory.Exists(destination))
{
fs.Directory.CreateDirectory(destination);
}
EnsureDestinationDirectoryExists(fileSystemReference, destination);

foreach (var entry in archive.Entries)
{
var entryFullName = Path.DirectorySeparatorChar switch
{
//adjust paths for unpacking on unix when packed on windows
'/' => entry.FullName.Replace("\\", "/"),
//adjust paths for unpacking on windows when packed on unix
'\\' => entry.FullName.Replace("/", "\\"),
_ => entry.FullName
};
var path = Path.Combine(destination, entryFullName);
var directoryName = Path.GetDirectoryName(path);
if (directoryName != null) fs.Directory.CreateDirectory(directoryName);
if (fs.File.Exists(path)) fs.File.Delete(path);
var extractedEntryPath = NormalizeExtractedEntryPathForWindowsAndUnix(entry);
var fullPath = Path.Combine(destination, extractedEntryPath);
CreateEntryDirectory(fileSystemReference, fullPath);

if (fileSystemReference.File.Exists(fullPath))
fileSystemReference.File.Delete(fullPath);

using var destStream = fs.File.Create(path);
using var destinationStream = fileSystemReference.File.Create(fullPath);
using var sourceStream = entry.Open();
sourceStream.CopyTo(destStream);
sourceStream.CopyTo(destinationStream);
}
}

/// <summary>
/// Creates a zip archive from a given directory on the filesystem <paramref name="fs"/>.
/// Creates a zip archive from the contents of a directory asynchronously.
/// </summary>
/// <param name="fs">The filesystem to operate on.</param>
/// <param name="source">The folder that should be packed into the zip archive.</param>
/// <param name="destination">The file path the zip archive should be written to.</param>
public static async Task CreateFromDirectoryAsync(IFileSystem fs, string source, string destination)
/// <param name="fileSystemReference">The file system abstraction used to access files and directories.</param>
/// <param name="source">The source directory to compress.</param>
/// <param name="destination">The path of the resulting zip archive.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="source"/> or <paramref name="destination"/> is null.</exception>
/// <exception cref="DirectoryNotFoundException">Thrown if the source directory does not exist.</exception>
/// <exception cref="UnauthorizedAccessException">Thrown if the caller does not have the required permissions.</exception>
/// <exception cref="IOException">Thrown if an I/O error occurs during compression.</exception>
public static async Task CreateFromDirectoryAsync(IFileSystem fileSystemReference, string source, string destination)
{
using var archive = GetWritableZipArchive(fs, destination);
var files = fs.Directory.GetFiles(source, "*", SearchOption.AllDirectories);
ValidatePath(source);
ValidatePath(destination);
using var archive = GetWritableZipArchive(fileSystemReference, destination);

var files = GetAllFilesExceptSymbolicLinksToPreventLoops(fileSystemReference, source);
foreach (var file in files)
{
var relativePath = Path.GetRelativePath(source, file);
var entry = archive.CreateEntry(relativePath);
await using var entryStream = entry.Open();
await using var fileStream = fs.File.OpenRead(file);
await fileStream.CopyToAsync(entryStream);
await AddFileToArchiveAsync(fileSystemReference, archive, relativePath, file);
}
}
private static void ValidatePath(string path)
{
if (path == null)
throw new ArgumentNullException(nameof(path), "Path cannot be null.");
if (path.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
{
throw new ArgumentException($"The path is invalid: {path}");
}
}
private static async Task AddFileToArchiveAsync(IFileSystem fileSystemReference, ZipArchive archive,
string relativePath, string file)
{
var entry = archive.CreateEntry(relativePath);
await using var entryStream = entry.Open();
await using var fileStream = fileSystemReference.File.OpenRead(file);
await fileStream.CopyToAsync(entryStream);
}

private static string[] GetAllFilesExceptSymbolicLinksToPreventLoops(IFileSystem fileSystemReference, string source)
{
return fileSystemReference.Directory.GetFiles(source, "*", SearchOption.AllDirectories)
.Where(file => !fileSystemReference.File.GetAttributes(file).HasFlag(FileAttributes.ReparsePoint))
.ToArray();
}


private static void CreateEntryDirectory(IFileSystem fileSystemReference, string fullPath)
{
var directoryName = Path.GetDirectoryName(fullPath);
if (directoryName != null) fileSystemReference.Directory.CreateDirectory(directoryName);
}

private static string NormalizeExtractedEntryPathForWindowsAndUnix(ZipArchiveEntry entry)
{
return Path.DirectorySeparatorChar switch
{
//adjust paths for unpacking on unix when packed on windows
'/' => entry.FullName.Replace("\\", "/"),
//adjust paths for unpacking on windows when packed on unix
'\\' => entry.FullName.Replace("/", "\\"),
_ => entry.FullName
};
}

private static void EnsureDestinationDirectoryExists(IFileSystem fileSystemReference, string destination)
{
if (!fileSystemReference.Directory.Exists(destination))
{
fileSystemReference.Directory.CreateDirectory(destination);
}
}
}
Loading
Loading