diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 0309e7a6e8d..1c2fc60513f 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@ - 10.1.2-beta01 + 10.1.2 diff --git a/src/BootstrapBlazor/Services/DefaultZipArchiveService.cs b/src/BootstrapBlazor/Services/DefaultZipArchiveService.cs index e872ab3eeda..9be54392501 100644 --- a/src/BootstrapBlazor/Services/DefaultZipArchiveService.cs +++ b/src/BootstrapBlazor/Services/DefaultZipArchiveService.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the Apache 2.0 License // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone @@ -29,12 +29,12 @@ public async Task ArchiveAsync(IEnumerable files, ArchiveOptions /// /// /// - /// 归档文件 + /// 归档文件 /// 要归档的文件集合 /// 归档配置 - public async Task ArchiveAsync(string archiveFileName, IEnumerable files, ArchiveOptions? options = null) + public async Task ArchiveAsync(string archiveFile, IEnumerable files, ArchiveOptions? options = null) { - using var stream = File.OpenWrite(archiveFileName); + using var stream = File.OpenWrite(archiveFile); await ArchiveFilesAsync(stream, files, options); } @@ -61,23 +61,79 @@ private static async Task ArchiveFilesAsync(Stream stream, IEnumerable f /// /// /// - /// 归档文件 - /// 要归档文件夹 + /// + /// /// /// /// + /// /// /// - public async Task ArchiveDirectory(string archiveFileName, string directoryName, CompressionLevel compressionLevel = CompressionLevel.Optimal, bool includeBaseDirectory = false, Encoding? encoding = null) + public async Task ArchiveDirectory(string archiveFile, string directoryName, CompressionLevel compressionLevel = CompressionLevel.Optimal, bool includeBaseDirectory = false, Encoding? encoding = null, CancellationToken token = default) { if (Directory.Exists(directoryName)) { - var folder = Path.GetDirectoryName(archiveFileName); + var folder = Path.GetDirectoryName(archiveFile); if (!string.IsNullOrEmpty(folder) && !Directory.Exists(folder)) { Directory.CreateDirectory(folder); } - await Task.Run(() => ZipFile.CreateFromDirectory(directoryName, archiveFileName, compressionLevel, includeBaseDirectory, encoding)); +#if NET10_0_OR_GREATER + await ZipFile.CreateFromDirectoryAsync(directoryName, archiveFile, compressionLevel, includeBaseDirectory, encoding, token); +#else + await Task.Run(() => + { + token.ThrowIfCancellationRequested(); + ZipFile.CreateFromDirectory(directoryName, archiveFile, compressionLevel, includeBaseDirectory, encoding); + }, token); +#endif + } + } + + /// + /// + /// + /// 归档文件 + /// + /// + /// + /// + /// + /// + /// + public async Task ArchiveDirectory(string archiveFile, IEnumerable entries, CompressionLevel compressionLevel = CompressionLevel.Optimal, Encoding? encoding = null, bool skipEmptyFolder = false, CancellationToken token = default) + { + using var archive = ZipFile.Open(archiveFile, ZipArchiveMode.Create, encoding); + + foreach (var entry in entries) + { + if (Directory.Exists(entry)) + { + AddFolderToZip(archive, entry, Path.GetFileName(entry), compressionLevel); + } + else if (File.Exists(entry)) + { + archive.CreateEntryFromFile(entry, Path.GetFileName(entry), compressionLevel); + } + } + } + + private static void AddFolderToZip(ZipArchive archive, string folderPath, string relativePath, CompressionLevel compressionLevel = CompressionLevel.Optimal) + { + archive.CreateEntry($"{relativePath}/", compressionLevel); + + // 添加当前文件夹中的所有文件 + foreach (string filePath in Directory.GetFiles(folderPath)) + { + string entryName = Path.Combine(relativePath, Path.GetFileName(filePath)); + archive.CreateEntryFromFile(filePath, entryName, compressionLevel); + } + + // 递归添加所有子文件夹 + foreach (string subfolderPath in Directory.GetDirectories(folderPath)) + { + string newRelativePath = Path.Combine(relativePath, Path.GetFileName(subfolderPath)); + AddFolderToZip(archive, subfolderPath, newRelativePath, compressionLevel); } } @@ -99,6 +155,34 @@ public bool ExtractToDirectory(string archiveFile, string destinationDirectoryNa return true; } + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task ExtractToDirectoryAsync(string archiveFile, string destinationDirectoryName, bool overwriteFiles = false, Encoding? encoding = null, CancellationToken token = default) + { + if (!Directory.Exists(destinationDirectoryName)) + { + Directory.CreateDirectory(destinationDirectoryName); + } + +#if NET10_0_OR_GREATER + await ZipFile.ExtractToDirectoryAsync(archiveFile, destinationDirectoryName, encoding, overwriteFiles, token); +#else + await Task.Run(() => + { + token.ThrowIfCancellationRequested(); + ZipFile.ExtractToDirectory(archiveFile, destinationDirectoryName, encoding, overwriteFiles); + }, token); +#endif + return true; + } + /// /// /// diff --git a/src/BootstrapBlazor/Services/IZipArchiveService.cs b/src/BootstrapBlazor/Services/IZipArchiveService.cs index 2514f9b6c17..e75593ffd26 100644 --- a/src/BootstrapBlazor/Services/IZipArchiveService.cs +++ b/src/BootstrapBlazor/Services/IZipArchiveService.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the Apache 2.0 License // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone @@ -24,21 +24,34 @@ public interface IZipArchiveService /// /// 将文件归档方法 /// - /// 归档文件 + /// 归档文件 /// 要归档的文件集合 /// 归档配置 - Task ArchiveAsync(string archiveFileName, IEnumerable files, ArchiveOptions? options = null); + Task ArchiveAsync(string archiveFile, IEnumerable files, ArchiveOptions? options = null); /// /// 将指定目录归档方法 /// - /// 归档文件 + /// 归档文件 /// 要归档文件夹 - /// - /// - /// + /// 压缩率 + /// 是否包含本目录 默认 false + /// 编码方式 默认 null 内部使用 UTF-8 + /// + /// + Task ArchiveDirectory(string archiveFile, string directoryName, CompressionLevel compressionLevel = CompressionLevel.Optimal, bool includeBaseDirectory = false, Encoding? encoding = null, CancellationToken token = default); + + /// + /// 将指定目录归档方法 + /// + /// 归档文件 + /// 要归档条目 + /// 压缩率 + /// 编码方式 默认 null 内部使用 UTF-8 + /// 是否跳过空文件夹 + /// /// - Task ArchiveDirectory(string archiveFileName, string directoryName, CompressionLevel compressionLevel = CompressionLevel.Optimal, bool includeBaseDirectory = false, Encoding? encoding = null); + Task ArchiveDirectory(string archiveFile, IEnumerable entries, CompressionLevel compressionLevel = CompressionLevel.Optimal, Encoding? encoding = null, bool skipEmptyFolder = false, CancellationToken token = default); /// /// 解压缩归档文件到指定文件夹 @@ -50,6 +63,17 @@ public interface IZipArchiveService /// bool ExtractToDirectory(string archiveFile, string destinationDirectoryName, bool overwriteFiles = false, Encoding? encoding = null); + /// + /// 解压缩归档文件到指定文件夹异步方法 + /// + /// 归档文件 + /// 解压缩文件夹 + /// 是否覆盖文件 默认 false 不覆盖 + /// 编码方式 默认 null 内部使用 UTF-8 + /// + /// + Task ExtractToDirectoryAsync(string archiveFile, string destinationDirectoryName, bool overwriteFiles = false, Encoding? encoding = null, CancellationToken token = default); + /// /// 获得归档压缩文件中指定归档文件 /// diff --git a/test/UnitTest/Services/ZipArchiveServiceTest.cs b/test/UnitTest/Services/ZipArchiveServiceTest.cs index c4b7c2e0ab3..142bc991f5b 100644 --- a/test/UnitTest/Services/ZipArchiveServiceTest.cs +++ b/test/UnitTest/Services/ZipArchiveServiceTest.cs @@ -51,6 +51,14 @@ public async Task Archive_Ok() archService.ExtractToDirectory(archiveFile, destFolder); Assert.True(Directory.Exists(destFolder)); + // 删除文件夹 + Directory.Delete(destFolder, true); + Assert.False(Directory.Exists(destFolder)); + + // 异步解压缩单元测试 + await archService.ExtractToDirectoryAsync(archiveFile, destFolder); + Assert.True(Directory.Exists(destFolder)); + // 打包文件夹单元测试 var tempFolder = Path.Combine(root, "test_temp"); if (Directory.Exists(tempFolder)) @@ -67,5 +75,27 @@ public async Task Archive_Ok() File.Delete(destFile); await Assert.ThrowsAsync(() => archService.ArchiveDirectory(null!, destFolder, includeBaseDirectory: true)); + + // 测试压缩多个文件夹 + var entries = new List() + { + destFolder, + tempFolder, + }; + entries.AddRange(files); + + destFile = Path.Combine(root, "folder.zip"); + if (File.Exists(destFile)) + { + File.Delete(destFile); + } + Assert.False(File.Exists(destFile)); + var subFolder = Path.Combine(tempFolder, "sub"); + if (!Directory.Exists(subFolder)) + { + Directory.CreateDirectory(subFolder); + } + await archService.ArchiveDirectory(destFile, entries); + Assert.True(File.Exists(destFile)); } }