19 private readonly SortedSet<string> _directories =
new(StringComparer.Ordinal) {
"/" };
24 private readonly Dictionary<string, NdsBuildFile> _files =
new(StringComparer.Ordinal);
30 public IReadOnlyCollection<string>
Directories =>
new ReadOnlyCollection<string>(_directories.ToArray());
39 public IReadOnlyCollection<NdsBuildFile>
Files =>
new ReadOnlyCollection<NdsBuildFile>(
40 _files.Values.OrderBy(
static file => file.Path, StringComparer.Ordinal).ToArray());
52 string sourceDirectory,
53 string destinationDirectory =
"/",
55 CancellationToken cancellationToken =
default)
57 ArgumentException.ThrowIfNullOrWhiteSpace(sourceDirectory);
58 string destination = NormalizePath(destinationDirectory, allowRoot:
true);
61 NdsHostDirectorySnapshot snapshot = await NdsHostDirectoryImporter.StageAsync(
65 cancellationToken).ConfigureAwait(
false);
66 return ApplyImport(snapshot, options.CollisionPolicy);
75 string normalized = NormalizePath(path, allowRoot:
false);
76 return _files.TryGetValue(normalized, out
NdsBuildFile? file)
78 :
throw new FileNotFoundException($
"NitroFS file was not found: {normalized}", normalized);
92 string normalized = NormalizePath(path, allowRoot:
true);
93 EnsureParents(normalized);
94 _directories.Add(normalized);
110 string normalized = NormalizePath(path, allowRoot:
false);
111 if (_files.ContainsKey(normalized) || _directories.Contains(normalized))
113 throw new IOException($
"NitroFS entry already exists: {normalized}");
116 string parent = GetParent(normalized);
117 EnsureParents(parent);
118 _directories.Add(parent);
119 _files.Add(normalized,
new(normalized, contents.ToArray()));
132 string normalized = NormalizePath(path, allowRoot:
false);
133 if (_directories.Contains(normalized))
135 throw new IOException($
"A directory already exists at {normalized}.");
138 string parent = GetParent(normalized);
139 EnsureParents(parent);
140 _directories.Add(parent);
141 _files[normalized] =
new(normalized, contents.ToArray());
153 string normalized = NormalizePath(path, allowRoot:
false);
154 if (!_files.Remove(normalized))
156 throw new FileNotFoundException($
"NitroFS file was not found: {normalized}", normalized);
172 string source = NormalizePath(sourcePath, allowRoot:
false);
173 string destination = NormalizePath(destinationPath, allowRoot:
false);
174 if (!_files.TryGetValue(source, out
NdsBuildFile? file))
176 throw new FileNotFoundException($
"NitroFS file was not found: {source}", source);
179 if (_files.ContainsKey(destination) || _directories.Contains(destination))
181 throw new IOException($
"NitroFS entry already exists: {destination}");
184 string parent = GetParent(destination);
185 EnsureParents(parent);
186 _directories.Add(parent);
187 _files.Remove(source);
188 file.Path = destination;
189 _files.Add(destination, file);
203 string source = NormalizePath(sourcePath, allowRoot:
false);
204 string destination = NormalizePath(destinationPath, allowRoot:
false);
205 if (!_directories.Contains(source))
207 throw new DirectoryNotFoundException($
"NitroFS directory was not found: {source}");
210 if (destination.StartsWith(source +
"/", StringComparison.Ordinal))
212 throw new IOException(
"A directory cannot be moved into its own subtree.");
215 if (_directories.Contains(destination) || _files.ContainsKey(destination))
217 throw new IOException($
"NitroFS entry already exists: {destination}");
220 EnsureParents(GetParent(destination));
221 string[] affectedDirectories = _directories
222 .Where(path => path == source || path.StartsWith(source +
"/", StringComparison.Ordinal))
225 .Where(file => file.Path.StartsWith(source +
"/", StringComparison.Ordinal))
227 foreach (
string directory
in affectedDirectories)
229 _directories.Remove(directory);
234 _files.Remove(file.
Path);
237 foreach (
string directory
in affectedDirectories)
239 _directories.Add(destination + directory[source.Length..]);
244 file.Path = destination + file.
Path[source.Length..];
245 _files.Add(file.
Path, file);
260 string normalized = NormalizePath(path, allowRoot:
false);
261 if (!_directories.Contains(normalized))
263 throw new DirectoryNotFoundException($
"NitroFS directory was not found: {normalized}");
266 string prefix = normalized +
"/";
267 if (_directories.Any(value => value.StartsWith(prefix, StringComparison.Ordinal)) ||
268 _files.Keys.Any(value => value.StartsWith(prefix, StringComparison.Ordinal)))
270 throw new IOException($
"NitroFS directory is not empty: {normalized}");
273 _directories.Remove(normalized);
283 internal NdsFileSystemBuildSnapshot BuildSnapshot(
int firstFileId = 0)
285 string[] directories = _directories.Order(StringComparer.Ordinal).ToArray();
286 if (directories.Length > 4096 || firstFileId < 0 || firstFileId + _files.Count > ushort.MaxValue + 1)
288 throw new InvalidDataException(
"NitroFS exceeds its 12-bit directory or 16-bit file-ID space.");
291 return NdsFileNameTableWriter.Write(directories, _files.Values.ToArray(), firstFileId);
298 private NdsDirectoryImportResult ApplyImport(
299 NdsHostDirectorySnapshot snapshot,
302 foreach (
string directory
in snapshot.Directories)
304 if (_files.ContainsKey(directory) ||
305 _files.Keys.Any(file => directory.StartsWith(file +
"/", StringComparison.Ordinal)))
307 throw new IOException($
"A file blocks imported directory path {directory}.");
311 foreach (NdsHostFileSnapshot file
in snapshot.Files)
313 if (_directories.Contains(file.Path) ||
314 _files.Keys.Any(existing => file.Path.StartsWith(existing +
"/", StringComparison.Ordinal)))
316 throw new IOException($
"A directory or parent file conflicts with imported payload {file.Path}.");
321 throw new IOException($
"A NitroFS file already exists at imported path {file.Path}.");
325 int directoryCount = snapshot.Directories.Count(directory => !_directories.Contains(directory));
326 foreach (
string directory
in snapshot.Directories)
332 int skipped = snapshot.SkippedLinks;
334 foreach (NdsHostFileSnapshot file
in snapshot.Files)
342 SetFile(file.Path, file.Contents);
344 bytes = checked(bytes + file.Contents.LongLength);
347 return new(fileCount, directoryCount, bytes, skipped);
355 private void EnsureParents(
string path)
362 if (_files.ContainsKey(path))
364 throw new IOException($
"A file already occupies required directory path {path}.");
367 string parent = GetParent(path);
368 EnsureParents(parent);
369 _directories.Add(parent);
379 internal static string NormalizePath(
string path,
bool allowRoot)
381 ArgumentException.ThrowIfNullOrWhiteSpace(path);
382 string normalized = path.Replace(
'\\',
'/');
383 if (!normalized.StartsWith(
'/'))
385 normalized =
"/" + normalized;
388 if ((!allowRoot && normalized ==
"/") ||
389 (normalized.Length > 1 && normalized.EndsWith(
'/')) ||
390 normalized.Contains(
"//", StringComparison.Ordinal))
392 throw new ArgumentException(
"NitroFS path is empty or ambiguous.", nameof(path));
395 foreach (
string segment
in normalized.Split(
'/', StringSplitOptions.RemoveEmptyEntries))
397 if (segment is
"." or
".." || segment.Length > 127 ||
398 segment.Any(
static value => value > 0xFF || value is
'/' or
'\\'))
400 throw new ArgumentException(
"NitroFS path contains a name that cannot be represented as one-byte FNT data.", nameof(path));
410 private static string GetParent(
string path)
412 int separator = path.LastIndexOf(
'/');
413 return separator == 0 ?
"/" : path[..separator];
async ValueTask< NdsDirectoryImportResult > ImportDirectoryAsync(string sourceDirectory, string destinationDirectory="/", NdsDirectoryImportOptions? options=null, CancellationToken cancellationToken=default)
Stages and transactionally merges a host directory into this NitroFS recipe. Calling the method repea...