-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPrefixedFileSystem.cs
More file actions
165 lines (131 loc) · 6.15 KB
/
PrefixedFileSystem.cs
File metadata and controls
165 lines (131 loc) · 6.15 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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
using System.Diagnostics;
using Ramstack.FileSystem.Null;
using Ramstack.FileSystem.Utilities;
namespace Ramstack.FileSystem.Prefixed;
/// <summary>
/// Represents an implementation of the <see cref="IVirtualFileSystem"/> that adds a specified prefix
/// to the file paths within the underlying file system.
/// </summary>
[DebuggerDisplay("{_prefix,nq}")]
public sealed class PrefixedFileSystem : IVirtualFileSystem
{
private readonly string _prefix;
private readonly IVirtualFileSystem _fs;
private readonly VirtualDirectory[] _directories;
/// <inheritdoc />
public bool IsReadOnly => _fs.IsReadOnly;
/// <summary>
/// Initializes a new instance of the <see cref="PrefixedFileSystem" /> class.
/// </summary>
/// <param name="prefix">The prefix to be applied to the file paths managed by this instance.</param>
/// <param name="fileSystem">The underlying file system that manages the files to which the prefix will be applied.</param>
public PrefixedFileSystem(string prefix, IVirtualFileSystem fileSystem)
{
prefix = VirtualPath.Normalize(prefix);
(_prefix, _fs) = (prefix, fileSystem);
// Create an artificial directory list
_directories = CreateArtificialDirectories(this, prefix);
static VirtualDirectory[] CreateArtificialDirectories(PrefixedFileSystem fs, string path)
{
var directories = new List<VirtualDirectory>();
VirtualDirectory? directory = null;
while (!string.IsNullOrEmpty(path))
{
directory = directory is null
? new PrefixedDirectory(fs, path, fs._fs.GetDirectory("/"))
: new ArtificialDirectory(fs, path, directory);
directories.Add(directory);
path = VirtualPath.GetDirectoryName(path);
}
return directories.ToArray();
}
}
/// <inheritdoc />
public VirtualFile GetFile(string path)
{
path = VirtualPath.Normalize(path);
var underlying = TryUnwrapPrefix(path, _prefix);
if (underlying is not null)
return new PrefixedFile(this, path, _fs.GetFile(underlying));
return new NotFoundFile(this, path);
}
/// <inheritdoc />
public VirtualDirectory GetDirectory(string path)
{
path = VirtualPath.Normalize(path);
foreach (var directory in _directories)
if (directory.FullName == path)
return directory;
var underlying = TryUnwrapPrefix(path, _prefix);
if (underlying is not null)
return new PrefixedDirectory(this, path, _fs.GetDirectory(underlying));
return new NotFoundDirectory(this, path);
}
/// <inheritdoc />
public void Dispose() =>
_fs.Dispose();
/// <summary>
/// Converts a path from the wrapped file system to a full path in this prefixed file system.
/// </summary>
/// <param name="underlyingPath">The path relative to the wrapped file system (must be normalized).</param>
/// <returns>
/// The full path including this file system's prefix.
/// </returns>
internal string WrapWithPrefix(string underlyingPath)
{
Debug.Assert(VirtualPath.IsNormalized(underlyingPath));
if (underlyingPath == "/")
return _prefix;
return VirtualPath.Join(_prefix, underlyingPath);
}
/// <summary>
/// Attempts to extract the underlying path by removing this file system's prefix from a full path.
/// </summary>
/// <param name="path">The full path that may include this file system's prefix (must be normalized).</param>
/// <param name="prefix">The prefix to compare against the path.</param>
/// <returns>
/// The underlying path relative to the wrapped file system if the prefix matches;
/// otherwise, <see langword="null"/> if the path doesn't belong to this prefixed file system.
/// </returns>
private static string? TryUnwrapPrefix(string path, string prefix)
{
Debug.Assert(VirtualPath.IsNormalized(path));
if (path == prefix)
return "/";
if (path.StartsWith(prefix, StringComparison.Ordinal) && path[prefix.Length] == '/')
return new string(path.AsSpan(prefix.Length));
return null;
}
#region Inner type: ArtificialDirectory
/// <summary>
/// Represents an artificial directory within a file system.
/// </summary>
private sealed class ArtificialDirectory : VirtualDirectory
{
private readonly PrefixedFileSystem _fs;
private readonly VirtualDirectory _directory;
/// <inheritdoc />
public override IVirtualFileSystem FileSystem => _fs;
/// <summary>
/// Initializes a new instance of the <see cref="ArtificialDirectory"/> class.
/// </summary>
/// <param name="fileSystem">The file system associated with this directory.</param>
/// <param name="path">The path of the directory.</param>
/// <param name="directory">The child directory.</param>
public ArtificialDirectory(PrefixedFileSystem fileSystem, string path, VirtualDirectory directory) : base(path) =>
(_fs, _directory) = (fileSystem, directory);
/// <inheritdoc />
protected override ValueTask<VirtualNodeProperties?> GetPropertiesCoreAsync(CancellationToken cancellationToken) =>
new ValueTask<VirtualNodeProperties?>(VirtualNodeProperties.None);
/// <inheritdoc />
protected override ValueTask CreateCoreAsync(CancellationToken cancellationToken) =>
default;
/// <inheritdoc />
protected override ValueTask DeleteCoreAsync(CancellationToken cancellationToken) =>
throw new UnauthorizedAccessException($"Access to the path '{FullName}' is denied.");
/// <inheritdoc />
protected override IAsyncEnumerable<VirtualNode> GetFileNodesCoreAsync(CancellationToken cancellationToken) =>
new VirtualNode[] { _directory }.ToAsyncEnumerable();
}
#endregion
}