Skip to content

Commit 1206206

Browse files
committed
feat(extraction): ✨ add recursive zip file enumeration
Introduce recursive file enumeration for ZIP archives, including support for nested ZIP files. Allows optional filtering of files and returns file streams for each entry. Ensures proper stream management and leverages System.IO.Compression for archive handling.
1 parent 6b3de39 commit 1206206

1 file changed

Lines changed: 60 additions & 0 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System.IO.Compression;
2+
3+
namespace EcoFlow.Mqtt.Api.Protobuf.Extraction;
4+
5+
public static class ZipReader
6+
{
7+
public static IEnumerable<(string FilePath, Stream FileStream)> EnumerateFilesRecursively(string zipFilePath, Predicate<string>? filterPredicate = null)
8+
{
9+
using var rootFileStream = new FileStream(zipFilePath, FileMode.Open, FileAccess.Read);
10+
11+
foreach (var result in EnumerateZipStream(rootFileStream, Path.GetFileName(zipFilePath), filterPredicate))
12+
yield return result;
13+
}
14+
15+
private static IEnumerable<(string FilePath, Stream FileStream)> EnumerateZipStream(Stream inputStream, string parentPath, Predicate<string>? filterPredicate)
16+
{
17+
using var zipArchive = new ZipArchive(inputStream, ZipArchiveMode.Read, leaveOpen: true);
18+
19+
foreach (var zipArchiveEntry in zipArchive.Entries)
20+
{
21+
if (string.IsNullOrEmpty(zipArchiveEntry.Name))
22+
continue;
23+
24+
var fullEntryPath = Path.Combine(parentPath, zipArchiveEntry.FullName);
25+
26+
var uncompressedStream = new MemoryStream();
27+
28+
using (var entryStream = zipArchiveEntry.Open())
29+
entryStream.CopyTo(uncompressedStream);
30+
31+
uncompressedStream.Position = 0;
32+
33+
if (IsZipHeader(uncompressedStream))
34+
{
35+
foreach (var nestedResult in EnumerateZipStream(uncompressedStream, fullEntryPath, filterPredicate))
36+
yield return nestedResult;
37+
}
38+
else
39+
{
40+
if (filterPredicate is null || filterPredicate(fullEntryPath))
41+
{
42+
uncompressedStream.Position = 0;
43+
yield return (fullEntryPath, uncompressedStream);
44+
}
45+
}
46+
}
47+
}
48+
49+
private static bool IsZipHeader(Stream stream)
50+
{
51+
if (stream.Length < 4)
52+
return false;
53+
54+
var buffer = (stackalloc byte[4]);
55+
stream.ReadExactly(buffer);
56+
stream.Position = 0;
57+
58+
return buffer[0] == 0x50 && buffer[1] == 0x4B && buffer[2] == 0x03 && buffer[3] == 0x04;
59+
}
60+
}

0 commit comments

Comments
 (0)