-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathZipFileSystem.cs
More file actions
134 lines (115 loc) · 5.03 KB
/
ZipFileSystem.cs
File metadata and controls
134 lines (115 loc) · 5.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
using System.IO.Compression;
using Ramstack.FileSystem.Null;
namespace Ramstack.FileSystem.Zip;
/// <summary>
/// Represents a file system backed by a ZIP archive.
/// </summary>
/// <remarks>
/// <b>WARNING:</b>
/// <para>
/// The <see cref="ZipFileSystem"/> is not thread-safe and allows reading only one file at a time, as it relies on
/// <see cref="ZipArchive"/>, which does not support parallel read operations or simultaneous opening of multiple streams.
/// </para>
/// <para>
/// You may use this class only if you can guarantee that:
/// <list type="bullet">
/// <item><description>Only one file is open for reading at a time.</description></item>
/// <item><description>No file is accessed concurrently.</description></item>
/// </list>
/// </para>
/// </remarks>
[Obsolete("Deprecated due to thread safety limitations and parallel file access capabilities.")]
public sealed class ZipFileSystem : IVirtualFileSystem
{
private readonly ZipArchive _archive;
private readonly Dictionary<string, VirtualNode> _directories;
/// <inheritdoc />
public bool IsReadOnly => true;
/// <summary>
/// Initializes a new instance of the <see cref="ZipFileSystem"/> class
/// using a ZIP archive located at the specified file path.
/// </summary>
/// <param name="path">The path to the ZIP archive file.</param>
public ZipFileSystem(string path)
: this(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ZipFileSystem"/> class
/// using a stream containing a ZIP archive.
/// </summary>
/// <param name="stream">The stream containing the ZIP archive.</param>
/// <param name="leaveOpen"><see langword="true" /> to leave the stream open
/// after the <see cref="ZipFileSystem"/> object is disposed; otherwise, <see langword="false" />.</param>
public ZipFileSystem(Stream stream, bool leaveOpen = false)
: this(new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ZipFileSystem"/> class
/// using an existing <see cref="ZipArchive"/>.
/// </summary>
/// <param name="archive">The <see cref="ZipArchive"/> instance
/// to use for providing access to ZIP archive content.</param>
public ZipFileSystem(ZipArchive archive)
{
_archive = archive;
_directories = new Dictionary<string, VirtualNode>
{
["/"] = new ZipDirectory(this, "/")
};
Initialize(archive, _directories);
}
/// <inheritdoc />
public VirtualFile GetFile(string path) =>
FindNode(path) as VirtualFile ?? new NotFoundFile(this, path);
/// <inheritdoc />
public VirtualDirectory GetDirectory(string path) =>
FindNode(path) as VirtualDirectory ?? new NotFoundDirectory(this, path);
/// <inheritdoc />
public void Dispose() =>
_archive.Dispose();
/// <summary>
/// Finds a <see cref="VirtualNode"/> by its path.
/// </summary>
/// <param name="path">The path of the node to find.</param>
/// <returns>
/// The <see cref="VirtualNode"/> if found; otherwise, <see langword="null"/>.
/// </returns>
private VirtualNode? FindNode(string path) =>
_directories.GetValueOrDefault(VirtualPath.Normalize(path));
/// <summary>
/// Initializes the file system with entries from the specified ZIP archive.
/// </summary>
/// <param name="archive">The ZIP archive to read entries from.</param>
/// <param name="cache">A dictionary to cache the directory and file nodes.</param>
private void Initialize(ZipArchive archive, Dictionary<string, VirtualNode> cache)
{
foreach (var entry in archive.Entries)
{
// Skipping directories
// --------------------
// Directory entries are denoted by a trailing slash '/' in their names.
//
// Since we can't rely on all archivers to include directory entries in archives,
// it's simpler to assume their absence and ignore any entries ending with a forward slash '/'.
if (entry.FullName.EndsWith('/'))
continue;
var path = VirtualPath.Normalize(entry.FullName);
var directory = GetOrCreateDirectory(VirtualPath.GetDirectoryName(path));
var file = new ZipFile(this, path, entry);
directory.RegisterNode(file);
cache.Add(path, file);
}
ZipDirectory GetOrCreateDirectory(string path)
{
if (cache.TryGetValue(path, out var di))
return (ZipDirectory)di;
di = new ZipDirectory(this, path);
var parent = GetOrCreateDirectory(VirtualPath.GetDirectoryName(path));
parent.RegisterNode(di);
cache.Add(path, di);
return (ZipDirectory)di;
}
}
}