diff --git a/src/BootstrapBlazor/Services/ArchiveEntry.cs b/src/BootstrapBlazor/Services/ArchiveEntry.cs
new file mode 100644
index 00000000000..efd21c63717
--- /dev/null
+++ b/src/BootstrapBlazor/Services/ArchiveEntry.cs
@@ -0,0 +1,27 @@
+// 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
+
+using System.IO.Compression;
+
+///
+/// 归档项实体类
+///
+public readonly record struct ArchiveEntry
+{
+ ///
+ /// 获得 物理文件
+ ///
+ public string SourceFileName { get; init; }
+
+ ///
+ /// 获得 归档项
+ ///
+ public string EntryName { get; init; }
+
+ ///
+ /// 获得 压缩配置
+ ///
+ public CompressionLevel? CompressionLevel { get; init; }
+}
diff --git a/src/BootstrapBlazor/Services/DefaultZipArchiveService.cs b/src/BootstrapBlazor/Services/DefaultZipArchiveService.cs
index 9be54392501..7316bcf7c05 100644
--- a/src/BootstrapBlazor/Services/DefaultZipArchiveService.cs
+++ b/src/BootstrapBlazor/Services/DefaultZipArchiveService.cs
@@ -13,15 +13,12 @@ class DefaultZipArchiveService : IZipArchiveService
///
///
///
- /// 要归档的文件集合
- /// 归档配置
- /// 归档数据流
- public async Task ArchiveAsync(IEnumerable files, ArchiveOptions? options = null)
+ public async Task ArchiveAsync(IEnumerable entries, ArchiveOptions? options = null)
{
var stream = new MemoryStream();
options ??= new ArchiveOptions();
options.LeaveOpen = true;
- await ArchiveFilesAsync(stream, files, options);
+ await ArchiveFilesAsync(stream, entries, options);
stream.Position = 0;
return stream;
}
@@ -29,31 +26,40 @@ public async Task ArchiveAsync(IEnumerable files, ArchiveOptions
///
///
///
- /// 归档文件
- /// 要归档的文件集合
- /// 归档配置
- public async Task ArchiveAsync(string archiveFile, IEnumerable files, ArchiveOptions? options = null)
+ public async Task ArchiveAsync(string archiveFile, IEnumerable entries, ArchiveOptions? options = null)
{
using var stream = File.OpenWrite(archiveFile);
- await ArchiveFilesAsync(stream, files, options);
+ await ArchiveFilesAsync(stream, entries, options);
}
- private static async Task ArchiveFilesAsync(Stream stream, IEnumerable files, ArchiveOptions? options = null)
+ private static async Task ArchiveFilesAsync(Stream stream, IEnumerable entries, ArchiveOptions? options = null)
{
options ??= new ArchiveOptions();
using var archive = new ZipArchive(stream, options.Mode, options.LeaveOpen, options.Encoding);
- foreach (var f in files)
+ foreach (var f in entries)
{
if (options.ReadStreamAsync != null)
{
- var entry = archive.CreateEntry(Path.GetFileName(f), options.CompressionLevel);
- using var entryStream = entry.Open();
- await using var content = await options.ReadStreamAsync(f);
+ var entry = archive.CreateEntry(f.EntryName, options.CompressionLevel);
+ await using var content = await options.ReadStreamAsync(f.SourceFileName);
+ await using var entryStream = entry.Open();
await content.CopyToAsync(entryStream);
}
- else
+ else if (Directory.Exists(f.SourceFileName))
{
- archive.CreateEntryFromFile(f, Path.GetFileName(f), options.CompressionLevel);
+ var entryName = f.EntryName;
+ if (!string.IsNullOrEmpty(entryName))
+ {
+ if (!entryName.EndsWith('/'))
+ {
+ entryName = $"{entryName}/";
+ }
+ archive.CreateEntry(entryName, f.CompressionLevel ?? options.CompressionLevel);
+ }
+ }
+ else if (File.Exists(f.SourceFileName))
+ {
+ archive.CreateEntryFromFile(f.SourceFileName, f.EntryName, f.CompressionLevel ?? options.CompressionLevel);
}
}
}
@@ -61,15 +67,7 @@ private static async Task ArchiveFilesAsync(Stream stream, IEnumerable f
///
///
///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public async Task ArchiveDirectory(string archiveFile, string directoryName, CompressionLevel compressionLevel = CompressionLevel.Optimal, bool includeBaseDirectory = false, Encoding? encoding = null, CancellationToken token = default)
+ public async Task ArchiveDirectoryAsync(string archiveFile, string directoryName, CompressionLevel compressionLevel = CompressionLevel.Optimal, bool includeBaseDirectory = false, Encoding? encoding = null, CancellationToken token = default)
{
if (Directory.Exists(directoryName))
{
@@ -93,77 +91,6 @@ await Task.Run(() =>
///
///
///
- /// 归档文件
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- 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);
- }
- }
-
- ///
- ///
- ///
- /// 归档文件
- /// 解压缩文件夹
- /// 是否覆盖文件 默认 false 不覆盖
- /// 编码方式 默认 null 内部使用 UTF-8
- ///
- public bool ExtractToDirectory(string archiveFile, string destinationDirectoryName, bool overwriteFiles = false, Encoding? encoding = null)
- {
- if (!Directory.Exists(destinationDirectoryName))
- {
- Directory.CreateDirectory(destinationDirectoryName);
- }
- ZipFile.ExtractToDirectory(archiveFile, destinationDirectoryName, encoding, overwriteFiles);
- return true;
- }
-
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
public async Task ExtractToDirectoryAsync(string archiveFile, string destinationDirectoryName, bool overwriteFiles = false, Encoding? encoding = null, CancellationToken token = default)
{
if (!Directory.Exists(destinationDirectoryName))
@@ -186,11 +113,6 @@ await Task.Run(() =>
///
///
///
- /// 归档文件
- /// 解压缩文件
- /// 是否覆盖文件 默认 false 不覆盖
- /// 编码方式 默认 null 内部使用 UTF-8
- ///
public ZipArchiveEntry? GetEntry(string archiveFile, string entryFile, bool overwriteFiles = false, Encoding? encoding = null)
{
using var archive = ZipFile.Open(archiveFile, ZipArchiveMode.Read, encoding);
diff --git a/src/BootstrapBlazor/Services/IZipArchiveService.cs b/src/BootstrapBlazor/Services/IZipArchiveService.cs
index e75593ffd26..1c07398afbe 100644
--- a/src/BootstrapBlazor/Services/IZipArchiveService.cs
+++ b/src/BootstrapBlazor/Services/IZipArchiveService.cs
@@ -16,18 +16,18 @@ public interface IZipArchiveService
///
/// 将文件归档方法
///
- /// 要归档的文件集合
+ /// 要归档项集合
/// 归档配置
/// 归档数据流
- Task ArchiveAsync(IEnumerable files, ArchiveOptions? options = null);
+ Task ArchiveAsync(IEnumerable entries, ArchiveOptions? options = null);
///
/// 将文件归档方法
///
/// 归档文件
- /// 要归档的文件集合
+ /// 要归档项集合
/// 归档配置
- Task ArchiveAsync(string archiveFile, IEnumerable files, ArchiveOptions? options = null);
+ Task ArchiveAsync(string archiveFile, IEnumerable entries, ArchiveOptions? options = null);
///
/// 将指定目录归档方法
@@ -38,30 +38,7 @@ public interface IZipArchiveService
/// 是否包含本目录 默认 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 archiveFile, IEnumerable entries, CompressionLevel compressionLevel = CompressionLevel.Optimal, Encoding? encoding = null, bool skipEmptyFolder = false, CancellationToken token = default);
-
- ///
- /// 解压缩归档文件到指定文件夹
- ///
- /// 归档文件
- /// 解压缩文件夹
- /// 是否覆盖文件 默认 false 不覆盖
- /// 编码方式 默认 null 内部使用 UTF-8
- ///
- bool ExtractToDirectory(string archiveFile, string destinationDirectoryName, bool overwriteFiles = false, Encoding? encoding = null);
+ Task ArchiveDirectoryAsync(string archiveFile, string directoryName, CompressionLevel compressionLevel = CompressionLevel.Optimal, bool includeBaseDirectory = false, Encoding? encoding = null, CancellationToken token = default);
///
/// 解压缩归档文件到指定文件夹异步方法
@@ -71,7 +48,6 @@ public interface IZipArchiveService
/// 是否覆盖文件 默认 false 不覆盖
/// 编码方式 默认 null 内部使用 UTF-8
///
- ///
Task ExtractToDirectoryAsync(string archiveFile, string destinationDirectoryName, bool overwriteFiles = false, Encoding? encoding = null, CancellationToken token = default);
///
@@ -81,6 +57,5 @@ public interface IZipArchiveService
/// 解压缩文件
/// 是否覆盖文件 默认 false 不覆盖
/// 编码方式 默认 null 内部使用 UTF-8
- ///
ZipArchiveEntry? GetEntry(string archiveFile, string entryFile, bool overwriteFiles = false, Encoding? encoding = null);
}
diff --git a/test/UnitTest/Services/ZipArchiveServiceTest.cs b/test/UnitTest/Services/ZipArchiveServiceTest.cs
index 142bc991f5b..0e5bd852e74 100644
--- a/test/UnitTest/Services/ZipArchiveServiceTest.cs
+++ b/test/UnitTest/Services/ZipArchiveServiceTest.cs
@@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
+using System.IO.Compression;
+
namespace UnitTest.Services;
public class ZipArchiveServiceTest : BootstrapBlazorTestBase
@@ -22,10 +24,15 @@ public async Task Archive_Ok()
using var fs = File.OpenWrite(f);
fs.WriteByte(65);
});
- var stream = await archService.ArchiveAsync(files);
+ var items = files.Select(i => new ArchiveEntry()
+ {
+ SourceFileName = i,
+ EntryName = Path.GetFileName(i)
+ });
+ var stream = await archService.ArchiveAsync(items);
Assert.NotNull(stream);
- stream = await archService.ArchiveAsync(files, new ArchiveOptions()
+ stream = await archService.ArchiveAsync(items, new ArchiveOptions()
{
CompressionLevel = System.IO.Compression.CompressionLevel.Optimal,
Encoding = System.Text.Encoding.UTF8,
@@ -35,7 +42,7 @@ public async Task Archive_Ok()
Assert.NotNull(stream);
var archiveFile = Path.Combine(root, "test.zip");
- await archService.ArchiveAsync(archiveFile, files);
+ await archService.ArchiveAsync(archiveFile, items);
Assert.True(File.Exists(archiveFile));
// GetEntry
@@ -48,7 +55,7 @@ public async Task Archive_Ok()
{
Directory.Delete(destFolder, true);
}
- archService.ExtractToDirectory(archiveFile, destFolder);
+ await archService.ExtractToDirectoryAsync(archiveFile, destFolder);
Assert.True(Directory.Exists(destFolder));
// 删除文件夹
@@ -70,32 +77,83 @@ public async Task Archive_Ok()
{
File.Delete(destFile);
}
- await archService.ArchiveDirectory(destFile, destFolder, includeBaseDirectory: true);
+ await archService.ArchiveDirectoryAsync(destFile, destFolder, includeBaseDirectory: true);
Assert.True(File.Exists(destFile));
File.Delete(destFile);
- await Assert.ThrowsAsync(() => archService.ArchiveDirectory(null!, destFolder, includeBaseDirectory: true));
+ await Assert.ThrowsAsync(() => archService.ArchiveDirectoryAsync(null!, destFolder, includeBaseDirectory: true));
+ }
- // 测试压缩多个文件夹
- var entries = new List()
+ [Fact]
+ public async Task ZipArchive_Ok()
+ {
+ var fileName = Path.Combine(AppContext.BaseDirectory, "test", "3.zip");
+ if (File.Exists(fileName))
{
- destFolder,
- tempFolder,
- };
- entries.AddRange(files);
+ File.Delete(fileName);
+ }
- destFile = Path.Combine(root, "folder.zip");
- if (File.Exists(destFile))
+ using var fs = File.OpenWrite(fileName);
+ using var zip = new ZipArchive(fs, ZipArchiveMode.Create);
+
+ var item = Path.Combine(AppContext.BaseDirectory, "test", "1.txt");
+ zip.CreateEntry("text/");
+ await zip.CreateEntryFromFileAsync(item, "text/1.txt");
+ }
+
+ [Fact]
+ public async Task ArchiveAsync_Ok()
+ {
+ var fileName = Path.Combine(AppContext.BaseDirectory, "archive_test", "test.zip");
+ if (File.Exists(fileName))
{
- File.Delete(destFile);
+ File.Delete(fileName);
}
- Assert.False(File.Exists(destFile));
- var subFolder = Path.Combine(tempFolder, "sub");
- if (!Directory.Exists(subFolder))
+
+ var root = AppContext.BaseDirectory;
+ var files = new string[]
{
- Directory.CreateDirectory(subFolder);
- }
- await archService.ArchiveDirectory(destFile, entries);
- Assert.True(File.Exists(destFile));
+ Path.Combine(root, "archive_test", "test1", "1.txt"),
+ Path.Combine(root, "archive_test", "test2", "2.txt")
+ };
+ files.ToList().ForEach(f =>
+ {
+ var folder = Path.GetDirectoryName(f);
+ if (!string.IsNullOrEmpty(folder) && !Directory.Exists(folder))
+ {
+ Directory.CreateDirectory(folder);
+ }
+ using var fs = File.OpenWrite(f);
+ fs.WriteByte(65);
+ });
+
+ var archService = Context.Services.GetRequiredService();
+ await archService.ArchiveAsync(fileName, new List()
+ {
+ new ArchiveEntry()
+ {
+ SourceFileName = files[0],
+ EntryName = "test1/test.log"
+ },
+ new ArchiveEntry()
+ {
+ SourceFileName = files[1],
+ EntryName = "test2/test.log",
+ CompressionLevel = CompressionLevel.Optimal
+ },
+ new ArchiveEntry()
+ {
+ SourceFileName = Path.Combine(AppContext.BaseDirectory, "archive_test", "test1"),
+ EntryName = "test1",
+ },
+ new ArchiveEntry()
+ {
+ SourceFileName = Path.Combine(AppContext.BaseDirectory, "archive_test", "test1"),
+ EntryName = "test2",
+ CompressionLevel = CompressionLevel.Optimal
+ }
+ });
+
+ Assert.True(File.Exists(fileName));
}
}