From 280cde58621c8389336ce2e789730594e8bf6d3d Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Sun, 8 Sep 2024 14:51:46 +0200 Subject: [PATCH] Add XML API documentation --- src/XenoAtom.UnixTools.Tests/CpioTests.cs | 3 +- .../TestFileSystem.cs | 8 +- src/XenoAtom.UnixTools/CpioEntryExtensions.cs | 3 + src/XenoAtom.UnixTools/CpioReader.cs | 5 - src/XenoAtom.UnixTools/CpioWriter.cs | 7 +- src/XenoAtom.UnixTools/UnixDeviceFile.cs | 3 + src/XenoAtom.UnixTools/UnixDirectory.cs | 123 +++++++++++- src/XenoAtom.UnixTools/UnixFile.cs | 6 + src/XenoAtom.UnixTools/UnixFileContent.cs | 76 ++++++++ src/XenoAtom.UnixTools/UnixFileContentKind.cs | 30 +++ src/XenoAtom.UnixTools/UnixFileExtensions.cs | 35 +++- src/XenoAtom.UnixTools/UnixFileSystemEntry.cs | 4 +- .../UnixInMemoryFileSystem.cs | 175 ++++++++++++++++++ src/XenoAtom.UnixTools/UnixInode.cs | 2 +- .../UnixMemoryFileSystem.cs | 91 --------- .../UnixMemoryFileSystemExtensions.cs | 32 +++- src/XenoAtom.UnixTools/UnixPath.cs | 120 +++++++++--- src/XenoAtom.UnixTools/UnixSymbolicLink.cs | 11 +- .../XenoAtom.UnixTools.csproj | 1 + 19 files changed, 590 insertions(+), 145 deletions(-) create mode 100644 src/XenoAtom.UnixTools/UnixInMemoryFileSystem.cs delete mode 100644 src/XenoAtom.UnixTools/UnixMemoryFileSystem.cs diff --git a/src/XenoAtom.UnixTools.Tests/CpioTests.cs b/src/XenoAtom.UnixTools.Tests/CpioTests.cs index d67cc91..14ac586 100644 --- a/src/XenoAtom.UnixTools.Tests/CpioTests.cs +++ b/src/XenoAtom.UnixTools.Tests/CpioTests.cs @@ -49,7 +49,7 @@ public void TestWithFileSystem() var fileStream = new MemoryStream(File.ReadAllBytes(@"cpio_archive_test.cpio")); var fileStreamOut = new MemoryStream(); - var fs = new UnixMemoryFileSystem(); + var fs = new UnixInMemoryFileSystem(); var entries = new List(); // Use a block to dispose the reader/writer @@ -121,6 +121,7 @@ public void TestWithFileSystem() Assert.AreEqual(entry.HardLinkCount, entryOut.HardLinkCount, "Invalid hard link count"); Assert.AreEqual(entry.Device, entryOut.Device, "Invalid device"); Assert.AreEqual(entry.ModificationTime, entryOut.ModificationTime, "Invalid modification time"); + Assert.AreEqual(entry.Checksum, entryOut.Checksum, "Invalid checksum"); Assert.AreEqual(entry.LinkName, entryOut.LinkName, "Invalid link name"); diff --git a/src/XenoAtom.UnixTools.Tests/TestFileSystem.cs b/src/XenoAtom.UnixTools.Tests/TestFileSystem.cs index 7044508..bcee832 100644 --- a/src/XenoAtom.UnixTools.Tests/TestFileSystem.cs +++ b/src/XenoAtom.UnixTools.Tests/TestFileSystem.cs @@ -10,7 +10,7 @@ public class TestFileSystem [TestMethod] public void TestSimple() { - var fs = new UnixMemoryFileSystem(); + var fs = new UnixInMemoryFileSystem(); var root = fs.RootDirectory; Assert.IsTrue(root.IsRoot); Assert.AreEqual("/", root.FullPath); @@ -222,10 +222,10 @@ public void TestRemoveHardLinkFile() Assert.AreEqual(1U, file3.HardLinkCount); Assert.IsTrue(file3.IsAttached); } - - private static UnixMemoryFileSystem CreateSimpleFileSystem() + + private static UnixInMemoryFileSystem CreateSimpleFileSystem() { - var fs = new UnixMemoryFileSystem(); + var fs = new UnixInMemoryFileSystem(); var root = fs.RootDirectory; var dir1 = root.CreateDirectory("dir1"); var file1 = dir1.CreateFile("file1", "HelloWorld"); diff --git a/src/XenoAtom.UnixTools/CpioEntryExtensions.cs b/src/XenoAtom.UnixTools/CpioEntryExtensions.cs index 66ad994..962f740 100644 --- a/src/XenoAtom.UnixTools/CpioEntryExtensions.cs +++ b/src/XenoAtom.UnixTools/CpioEntryExtensions.cs @@ -4,6 +4,9 @@ namespace XenoAtom.UnixTools; +/// +/// Extensions for . +/// public static class CpioEntryExtensions { /// diff --git a/src/XenoAtom.UnixTools/CpioReader.cs b/src/XenoAtom.UnixTools/CpioReader.cs index e98776a..4e40aad 100644 --- a/src/XenoAtom.UnixTools/CpioReader.cs +++ b/src/XenoAtom.UnixTools/CpioReader.cs @@ -37,11 +37,6 @@ public CpioReader(Stream stream, bool leaveOpen = false) _positionInSuperStream = stream.CanSeek ? stream.Position : 0; } - /// - /// Gets the underlying stream. - /// - public Stream Stream => _stream; - /// /// Tries to get the next entry from the CPIO archive. /// diff --git a/src/XenoAtom.UnixTools/CpioWriter.cs b/src/XenoAtom.UnixTools/CpioWriter.cs index 528d445..c5689f7 100644 --- a/src/XenoAtom.UnixTools/CpioWriter.cs +++ b/src/XenoAtom.UnixTools/CpioWriter.cs @@ -11,7 +11,7 @@ namespace XenoAtom.UnixTools; /// -/// Provides a raw writer for CPIO archives. For a higher level API, use . +/// Provides a raw writer for CPIO archives. For a higher level API, use and . /// public sealed unsafe class CpioWriter : IDisposable { @@ -39,11 +39,6 @@ public CpioWriter(Stream stream, bool leaveOpen = false) _positionInSuperStream = stream.CanSeek ? stream.Position : 0; } - /// - /// Gets the underlying stream. - /// - public Stream Stream => _stream; - /// /// Adds a new entry to the CPIO archive. /// diff --git a/src/XenoAtom.UnixTools/UnixDeviceFile.cs b/src/XenoAtom.UnixTools/UnixDeviceFile.cs index 44b72e5..3f1d415 100644 --- a/src/XenoAtom.UnixTools/UnixDeviceFile.cs +++ b/src/XenoAtom.UnixTools/UnixDeviceFile.cs @@ -4,6 +4,9 @@ namespace XenoAtom.UnixTools; +/// +/// A Unix device file. +/// public sealed class UnixDeviceFile : UnixFileSystemEntry { internal UnixDeviceFile(string name, UnixInode node) : base(name, node) diff --git a/src/XenoAtom.UnixTools/UnixDirectory.cs b/src/XenoAtom.UnixTools/UnixDirectory.cs index 4a24603..4c91117 100644 --- a/src/XenoAtom.UnixTools/UnixDirectory.cs +++ b/src/XenoAtom.UnixTools/UnixDirectory.cs @@ -11,22 +11,40 @@ namespace XenoAtom.UnixTools; +/// +/// A Unix directory. +/// public sealed class UnixDirectory : UnixFileSystemEntry { private const int MaximumPathDepth = 2048; + /// + /// The default mode used when creating a directory. + /// public const UnixFileMode DefaultMode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.GroupRead | UnixFileMode.GroupExecute | UnixFileMode.OtherRead | UnixFileMode.OtherExecute; internal UnixDirectory(string name, UnixInode node) : base(name, node) { } - + + /// + /// Gets a boolean indicating whether this directory is the root directory. + /// public bool IsRoot => Parent == null && Name.Length == 0; + /// + /// Gets the entries of this directory. + /// public SortedDictionary.ValueCollection Entries => InternalEntries.Values; internal SortedDictionary InternalEntries => Inode.GetDictionaryContent(); - + + /// + /// Creates a file in this directory. + /// + /// A path relative to this directory. + /// A boolean indicating whether intermediate directories should be created. + /// The created entry. public UnixFile CreateFile(string path, bool createIntermediateDirectories = false) { var (dir, name) = ResolveEntryForCreate(path, createIntermediateDirectories); @@ -35,6 +53,13 @@ public UnixFile CreateFile(string path, bool createIntermediateDirectories = fal return file; } + /// + /// Creates a file in this directory. + /// + /// A path relative to this directory. + /// The content of the file. + /// A boolean indicating whether intermediate directories should be created. + /// The created entry. public UnixFile CreateFile(string path, UnixFileContent content, bool createIntermediateDirectories = false) { var (dir, name) = ResolveEntryForCreate(path, createIntermediateDirectories); @@ -44,6 +69,12 @@ public UnixFile CreateFile(string path, UnixFileContent content, bool createInte return file; } + /// + /// Creates a directory in this directory. + /// + /// A path relative to this directory. + /// A boolean indicating whether intermediate directories should be created. + /// The created entry. public UnixDirectory CreateDirectory(string path, bool createIntermediateDirectories = false) { var (dir, name) = ResolveEntryForCreate(path, createIntermediateDirectories); @@ -53,6 +84,15 @@ public UnixDirectory CreateDirectory(string path, bool createIntermediateDirecto return directory; } + /// + /// Creates a device file in this directory. + /// + /// A path relative to this directory. + /// The kind of device file. The value must be or + /// The device id. + /// A boolean indicating whether intermediate directories should be created. + /// The created entry. + /// If is not or . public UnixDeviceFile CreateDevice(string path, UnixFileKind kind, DeviceId id, bool createIntermediateDirectories = false) { if (kind != UnixFileKind.CharacterSpecialDevice && kind != UnixFileKind.BlockSpecialDevice) @@ -69,16 +109,33 @@ public UnixDeviceFile CreateDevice(string path, UnixFileKind kind, DeviceId id, return device; } + /// + /// Creates a symbolic link in this directory. + /// + /// A path relative to this directory. + /// The target of the symbolic link. + /// A boolean indicating whether intermediate directories should be created. + /// The created entry. public UnixSymbolicLink CreateSymbolicLink(string path, string target, bool createIntermediateDirectories = false) { var (dir, name) = ResolveEntryForCreate(path, createIntermediateDirectories); // A symbolic link doesn't check if the target exists + UnixPath.Validate(target, nameof(target)); + target = UnixPath.Normalize(target); // Make sure that the link is noramlized var node = CreateNode(UnixFileKind.SymbolicLink, target); var link = new UnixSymbolicLink(name!, node); dir.AddEntry(link); return link; } + /// + /// Creates a hard link in this directory. + /// + /// + /// A path relative to this directory. + /// The target of this hardlink. + /// A boolean indicating whether intermediate directories should be created. + /// The created entry. public TEntry CreateHardLink(string path, TEntry target, bool createIntermediateDirectories = false) where TEntry: UnixFileSystemEntry { var (dir, name) = ResolveEntryForCreate(path, createIntermediateDirectories); @@ -87,11 +144,22 @@ public TEntry CreateHardLink(string path, TEntry target, bool createInte return link; } + /// + /// Checks if an entry exists in this directory. + /// + /// A path relative to this directory. + /// A boolean indicating whether the entry exists. public bool ContainsEntry(string path) { return TryGetEntry(path, out _); } + /// + /// Tries to get an entry in this directory. + /// + /// A path relative to this directory. + /// The entry if found. + /// A boolean indicating whether the entry was found. public bool TryGetEntry(string path, [NotNullWhen(true)] out UnixFileSystemEntry? entry) { var dir = this; @@ -106,8 +174,19 @@ public bool TryGetEntry(string path, [NotNullWhen(true)] out UnixFileSystemEntry return dir.InternalEntries.TryGetValue(name!, out entry); } + /// + /// Gets an entry in this directory. + /// + /// A path relative to this directory. + /// The entry found. public UnixFileSystemEntry this[string path] => GetEntry(path); + /// + /// Gets an entry in this directory. + /// + /// A path relative to this directory. + /// The entry found. + /// If the entry does not exist. public UnixFileSystemEntry GetEntry(string path) { if (!TryGetEntry(path, out var entry)) @@ -117,6 +196,17 @@ public UnixFileSystemEntry GetEntry(string path) return entry; } + /// + /// Copies an entry to a destination path. + /// + /// The source path relative to this directory. + /// The destination path relative to this directory. + /// The copy mode. + /// A boolean indicating whether to overwrite the destination entry if it already exists. + /// If the source path does not exist. + /// + /// If the destination path is a directory, the source entry is copied into it. + /// public void CopyEntry(string sourcePath, string destinationPath, UnixCopyMode mode = UnixCopyMode.Single, bool overwrite = false) { VerifyAttached(); @@ -218,6 +308,17 @@ public void CopyEntry(string sourcePath, string destinationPath, UnixCopyMode mo } } + /// + /// Moves an entry to a destination path. + /// + /// The source path relative to this directory. + /// The destination path relative to this directory. + /// A boolean indicating whether intermediate directories should be created. + /// A boolean indicating whether to overwrite the destination entry if it already exists. + /// If the source path does not exist. + /// + /// If the destination path is a directory, the source entry is moved into it. + /// public void MoveEntry(string sourcePath, string destinationPath, bool createIntermediateDirectories = false, bool overwrite = false) { VerifyAttached(); @@ -271,6 +372,11 @@ public void MoveEntry(string sourcePath, string destinationPath, bool createInte sourceEntry.SetParent(destinationDirectory, destinationName!); } + /// + /// Deletes an entry in this directory. + /// + /// A path relative to this directory. + /// If the entry is the root directory. public void DeleteEntry(string path) { var entry = GetEntry(path); @@ -281,6 +387,11 @@ public void DeleteEntry(string path) entry.SetParent(null); } + /// + /// Enumerates the file system entries in this directory. + /// + /// The search option. + /// An enumeration of file system entries. public IEnumerable EnumerateFileSystemEntries(SearchOption searchOption = SearchOption.TopDirectoryOnly) { if (searchOption == SearchOption.TopDirectoryOnly) @@ -306,6 +417,12 @@ public IEnumerable EnumerateFileSystemEntries(SearchOption } } + /// + /// Enumerates the file system entries in this directory. + /// + /// The search pattern. + /// The search option. + /// An enumeration of file system entries. public IEnumerable EnumerateFileSystemEntries(string searchPattern, SearchOption searchOption = SearchOption.TopDirectoryOnly) { // Make a copy (to allow adding/removing entries while iterating) @@ -540,7 +657,7 @@ private void ThrowEntryAlreadyExists(UnixDirectory directory, string name) throw new ArgumentException($"An entry with the name `{name}` already exists in the directory `{directory.FullPath}`"); } - internal static UnixDirectory CreateRoot(UnixMemoryFileSystem fs) + internal static UnixDirectory CreateRoot(UnixInMemoryFileSystem fs) { var node = new UnixInode(0, UnixFileKind.Directory, new SortedDictionary(StringComparer.Ordinal)) { diff --git a/src/XenoAtom.UnixTools/UnixFile.cs b/src/XenoAtom.UnixTools/UnixFile.cs index 9e59a0e..437cace 100644 --- a/src/XenoAtom.UnixTools/UnixFile.cs +++ b/src/XenoAtom.UnixTools/UnixFile.cs @@ -4,8 +4,14 @@ namespace XenoAtom.UnixTools; +/// +/// A Unix file. +/// public sealed class UnixFile : UnixFileSystemEntry { + /// + /// The default mode when creating a new file. + /// public const UnixFileMode DefaultMode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead; internal UnixFile(string name, UnixInode node) : base(name, node) diff --git a/src/XenoAtom.UnixTools/UnixFileContent.cs b/src/XenoAtom.UnixTools/UnixFileContent.cs index e240525..77f89a1 100644 --- a/src/XenoAtom.UnixTools/UnixFileContent.cs +++ b/src/XenoAtom.UnixTools/UnixFileContent.cs @@ -6,26 +6,59 @@ namespace XenoAtom.UnixTools; +/// +/// Represents the content of a Unix file. +/// public readonly struct UnixFileContent { private readonly object? _data; + /// + /// An empty content. + /// public static UnixFileContent Empty => default; internal UnixFileContent(object? data) => _data = data; + /// + /// Initializes a new instance of with a string. + /// + /// The string as text representing the data. public UnixFileContent(string data) => _data = data; + /// + /// Initializes a new instance of with a byte array. + /// + /// The byte array representing the data. public UnixFileContent(byte[] data) => _data = data; + /// + /// Initializes a new instance of with a stream. + /// + /// The stream representing the data. public UnixFileContent(Stream data) => _data = data; + /// + /// Initializes a new instance of with a function returning a string. + /// + /// The function returning a string representing the data. public UnixFileContent(Func data) => _data = data; + /// + /// Initializes a new instance of with a function returning a byte array. + /// + /// The function returning a byte array representing the data. public UnixFileContent(Func data) => _data = data; + /// + /// Initializes a new instance of with a function returning a stream. + /// + /// The function returning a stream representing the data. public UnixFileContent(Func data) => _data = data; + /// + /// Gets the kind of content. + /// public UnixFileContentKind Kind { get @@ -43,8 +76,17 @@ public UnixFileContentKind Kind } } + /// + /// Gets the raw content data. + /// public object? Data => _data; + /// + /// Copies the content to a stream. + /// + /// The output stream + /// The encoding to use when copying a string content. + /// If the content kind is invalid. public void CopyTo(Stream stream, Encoding? encoding = null) { switch (_data) @@ -95,6 +137,12 @@ public void CopyTo(Stream stream, Encoding? encoding = null) } } + /// + /// Copies the content to a stream. + /// + /// The output stream + /// The encoding to use when copying a string content. + /// If the content kind is invalid. public async ValueTask CopyToAsync(Stream stream, Encoding? encoding = null) { switch (_data) @@ -154,6 +202,9 @@ internal UnixFileContent CreateCopy() }; } + /// + /// Disposes the content (only if the content is a stream). + /// public void Dispose() { if (_data is Stream stream) @@ -162,6 +213,7 @@ public void Dispose() } } + /// public override string ToString() { return _data switch @@ -176,15 +228,39 @@ public override string ToString() }; } + /// + /// Implicit conversion from a string to . + /// + /// The string to convert. public static implicit operator UnixFileContent(string data) => new(data); + /// + /// Implicit conversion from a byte array to . + /// + /// The byte array to convert. public static implicit operator UnixFileContent(byte[] data) => new(data); + /// + /// Implicit conversion from a stream to . + /// + /// The stream to convert. public static implicit operator UnixFileContent(Stream data) => new(data); + /// + /// Implicit conversion from a function returning a string to . + /// + /// The function returning a string to convert. public static implicit operator UnixFileContent(Func data) => new(data); + /// + /// Implicit conversion from a function returning a byte array to . + /// + /// The function returning a byte array to convert. public static implicit operator UnixFileContent(Func data) => new(data); + /// + /// Implicit conversion from a function returning a stream to . + /// + /// The function returning a stream to convert. public static implicit operator UnixFileContent(Func data) => new(data); } \ No newline at end of file diff --git a/src/XenoAtom.UnixTools/UnixFileContentKind.cs b/src/XenoAtom.UnixTools/UnixFileContentKind.cs index 0235dab..94f714e 100644 --- a/src/XenoAtom.UnixTools/UnixFileContentKind.cs +++ b/src/XenoAtom.UnixTools/UnixFileContentKind.cs @@ -4,13 +4,43 @@ namespace XenoAtom.UnixTools; +/// +/// Defines the kind of content for a Unix file. +/// public enum UnixFileContentKind { + /// + /// The content is empty. + /// Empty, + + /// + /// The content is a string. + /// String, + + /// + /// The content is a byte array. + /// ByteArray, + + /// + /// The content is a stream. + /// Stream, + + /// + /// The content is a function returning a string (). + /// FuncString, + + /// + /// The content is a function returning a byte array (). + /// FuncByteArray, + + /// + /// The content is a (). + /// FuncStream } \ No newline at end of file diff --git a/src/XenoAtom.UnixTools/UnixFileExtensions.cs b/src/XenoAtom.UnixTools/UnixFileExtensions.cs index e5faa35..9ce1ac8 100644 --- a/src/XenoAtom.UnixTools/UnixFileExtensions.cs +++ b/src/XenoAtom.UnixTools/UnixFileExtensions.cs @@ -6,14 +6,31 @@ namespace XenoAtom.UnixTools; +/// +/// Defines extension methods for . +/// public static class UnixFileExtensions { - public static byte[] ReadAllBytes(this UnixFileContent content) + /// + /// Reads all the bytes from the content of this file. + /// + /// The Unix content of this file. + /// The encoding to use to convert string content. + /// The content as a byte array. + public static byte[] ReadAllBytes(this UnixFileContent content, Encoding? encoding = null) { var stream = new MemoryStream(); - content.CopyTo(stream); + encoding ??= Encoding.UTF8; + content.CopyTo(stream, encoding); return stream.ToArray(); } + + /// + /// Reads all the text from the content of this file. + /// + /// The Unix content of this file. + /// The encoding to use to convert string content. + /// The content as a text. public static string ReadAllText(this UnixFileContent content, Encoding? encoding = null) { encoding ??= Encoding.UTF8; @@ -22,7 +39,19 @@ public static string ReadAllText(this UnixFileContent content, Encoding? encodin return encoding.GetString(stream.ToArray()); } - public static byte[] ReadAllBytes(this UnixFile file) => file.Content.ReadAllBytes(); + /// + /// Reads all the bytes from the content of this file. + /// + /// The Unix file. + /// The encoding to use to convert string content. + /// The content as a byte array. + public static byte[] ReadAllBytes(this UnixFile file, Encoding? encoding = null) => file.Content.ReadAllBytes(encoding); + /// + /// Reads all the text from the content of this file. + /// + /// The Unix file. + /// The encoding to use to convert string content. + /// The content as a text. public static string ReadAllText(this UnixFile file, Encoding? encoding = null) => file.Content.ReadAllText(encoding); } \ No newline at end of file diff --git a/src/XenoAtom.UnixTools/UnixFileSystemEntry.cs b/src/XenoAtom.UnixTools/UnixFileSystemEntry.cs index 8d596ed..c54b455 100644 --- a/src/XenoAtom.UnixTools/UnixFileSystemEntry.cs +++ b/src/XenoAtom.UnixTools/UnixFileSystemEntry.cs @@ -16,7 +16,7 @@ public abstract class UnixFileSystemEntry private string _name; private readonly UnixInode _inode; private UnixDirectory? _parent; - private UnixMemoryFileSystem? _fileSystem; + private UnixInMemoryFileSystem? _fileSystem; internal UnixFileSystemEntry(string name, UnixInode node) { @@ -71,7 +71,7 @@ public string FullPath /// /// Gets the file system associated to this entry. /// - public UnixMemoryFileSystem? FileSystem + public UnixInMemoryFileSystem? FileSystem { get => _fileSystem; internal set => _fileSystem = value; diff --git a/src/XenoAtom.UnixTools/UnixInMemoryFileSystem.cs b/src/XenoAtom.UnixTools/UnixInMemoryFileSystem.cs new file mode 100644 index 0000000..bd3c3f2 --- /dev/null +++ b/src/XenoAtom.UnixTools/UnixInMemoryFileSystem.cs @@ -0,0 +1,175 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. + +using System.Diagnostics.CodeAnalysis; + +namespace XenoAtom.UnixTools; + +/// +/// An in-memory Unix file system. +/// +public sealed class UnixInMemoryFileSystem +{ + /// + /// Creates a new instance of . + /// + public UnixInMemoryFileSystem() + { + NextInodeIndex = 1; // We start at 2 to avoid the root inode 1 + RootDirectory = UnixDirectory.CreateRoot(this); + } + + internal long NextInodeIndex { get; set; } + + /// + /// Gets the root directory of this file system. + /// + public UnixDirectory RootDirectory { get; } + + /// + /// Creates a new file at the specified path. + /// + /// An absolute path. + /// A boolean indicating whether intermediate directories should be created. + /// The created file. + public UnixFile CreateFile(string fullPath, bool createIntermediateDirectories = true) => CreateFile(fullPath, UnixFileContent.Empty, createIntermediateDirectories); + + /// + /// Creates a new file at the specified path. + /// + /// An absolute path. + /// + /// A boolean indicating whether intermediate directories should be created. + /// The created file. + public UnixFile CreateFile(string fullPath, UnixFileContent content, bool createIntermediateDirectories = true) + { + ValidateFullPath(fullPath); + return RootDirectory.CreateFile(fullPath, content, createIntermediateDirectories); + } + + /// + /// Creates a new directory at the specified path. + /// + /// An absolute path. + /// A boolean indicating whether intermediate directories should be created. + /// The created directory. + public UnixDirectory CreateDirectory(string fullPath, bool createIntermediateDirectories = true) + { + ValidateFullPath(fullPath); + return RootDirectory.CreateDirectory(fullPath, createIntermediateDirectories); + } + + /// + /// Creates a new device at the specified path. + /// + /// An absolute path. + /// The kind of device file. The value must be or + /// The device id. + /// A boolean indicating whether intermediate directories should be created. + /// The created device. + public UnixDeviceFile CreateDevice(string fullPath, UnixFileKind kind, DeviceId id, bool createIntermediateDirectories = true) + { + ValidateFullPath(fullPath); + return RootDirectory.CreateDevice(fullPath, kind, id, createIntermediateDirectories); + } + + /// + /// Creates a new symbolic link at the specified path. + /// + /// An absolute path. + /// The target of the symbolic link. + /// A boolean indicating whether intermediate directories should be created. + /// The created device. + public UnixSymbolicLink CreateSymbolicLink(string fullPath, string target, bool createIntermediateDirectories = true) + { + ValidateFullPath(fullPath); + return RootDirectory.CreateSymbolicLink(fullPath, target, createIntermediateDirectories); + } + + /// + /// Creates a new hard link at the specified path. + /// + /// The type of the target entry. + /// An absolute path. + /// The target of the hardlink. + /// A boolean indicating whether intermediate directories should be created. + /// The created entry. + public TEntry CreateHardLink(string fullPath, TEntry target, bool createIntermediateDirectories = true) where TEntry : UnixFileSystemEntry + { + ValidateFullPath(fullPath); + return RootDirectory.CreateHardLink(fullPath, target, createIntermediateDirectories); + } + + /// + /// Tries to get an entry at the specified path. + /// + /// An absolute path. + /// The entry if found. + /// True if the entry is found, false otherwise. + public bool TryGetEntry(string fullPath, [NotNullWhen(true)] out UnixFileSystemEntry? entry) + { + ValidateFullPath(fullPath); + return RootDirectory.TryGetEntry(fullPath, out entry); + } + + /// + /// Gets an entry at the specified path. + /// + /// An absolute path. + /// The entry. + public UnixFileSystemEntry GetEntry(string fullPath) + { + ValidateFullPath(fullPath); + return RootDirectory.GetEntry(fullPath); + } + + /// + /// Deletes an entry at the specified path. + /// + /// An absolute path. + public void DeleteEntry(string fullPath) + { + ValidateFullPath(fullPath); + RootDirectory.DeleteEntry(fullPath); + } + + /// + /// Copies an entry from the source path to the destination path. + /// + /// The source path. + /// The destination path. + /// The copy mode. + /// + /// If the destination path is a directory, the source entry is coped into it. + /// + public void CopyEntry(string sourcePath, string destinationPath, UnixCopyMode mode = UnixCopyMode.Single) + { + ValidateFullPath(sourcePath, nameof(sourcePath)); + ValidateFullPath(destinationPath, nameof(destinationPath)); + RootDirectory.CopyEntry(sourcePath, destinationPath, mode); + } + + /// + /// Moves an entry from the source path to the destination path. + /// + /// The source path. + /// The destination path. + /// A boolean indicating whether intermediate directories should be created. + /// + /// If the destination path is a directory, the source entry is moved into it. + /// + public void MoveEntry(string sourcePath, string destinationPath, bool createIntermediateDirectories = false) + { + ValidateFullPath(sourcePath, nameof(sourcePath)); + ValidateFullPath(destinationPath, nameof(destinationPath)); + RootDirectory.MoveEntry(sourcePath, destinationPath, createIntermediateDirectories); + } + + private static void ValidateFullPath(string fullPath, string? paramName = "fullPath") + { + ArgumentNullException.ThrowIfNull(fullPath); + if (fullPath.Length == 0) throw new ArgumentException("The path cannot be empty", paramName); + if (!UnixPath.IsPathRooted(fullPath)) throw new ArgumentException("The path must be absolute and start with a `/`", paramName); + } +} \ No newline at end of file diff --git a/src/XenoAtom.UnixTools/UnixInode.cs b/src/XenoAtom.UnixTools/UnixInode.cs index 1fb39f0..36b7901 100644 --- a/src/XenoAtom.UnixTools/UnixInode.cs +++ b/src/XenoAtom.UnixTools/UnixInode.cs @@ -115,7 +115,7 @@ internal void SetDeviceId(DeviceId id) _deviceId = id; } - internal UnixInode CreateCopy(UnixMemoryFileSystem fs) + internal UnixInode CreateCopy(UnixInMemoryFileSystem fs) { object? content = FileKind switch { diff --git a/src/XenoAtom.UnixTools/UnixMemoryFileSystem.cs b/src/XenoAtom.UnixTools/UnixMemoryFileSystem.cs deleted file mode 100644 index bb98d92..0000000 --- a/src/XenoAtom.UnixTools/UnixMemoryFileSystem.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. -// Licensed under the BSD-Clause 2 license. -// See license.txt file in the project root for full license information. - -using System.Diagnostics.CodeAnalysis; - -namespace XenoAtom.UnixTools; - -public sealed class UnixMemoryFileSystem -{ - public UnixMemoryFileSystem() - { - NextInodeIndex = 1; // We start at 2 to avoid the root inode 1 - RootDirectory = UnixDirectory.CreateRoot(this); - } - - internal long NextInodeIndex { get; set; } - - public UnixDirectory RootDirectory { get; } - - public UnixDirectory CreateDirectory(string fullPath, bool createIntermediateDirectories = true) - { - ValidateFullPath(fullPath); - return RootDirectory.CreateDirectory(fullPath, createIntermediateDirectories); - } - - public UnixFile CreateFile(string fullPath, bool createIntermediateDirectories = true) => CreateFile(fullPath, UnixFileContent.Empty, createIntermediateDirectories); - - public UnixFile CreateFile(string fullPath, UnixFileContent content, bool createIntermediateDirectories = true) - { - ValidateFullPath(fullPath); - return RootDirectory.CreateFile(fullPath, content, createIntermediateDirectories); - } - - public UnixDeviceFile CreateDevice(string fullPath, UnixFileKind kind, DeviceId id, bool createIntermediateDirectories = true) - { - ValidateFullPath(fullPath); - return RootDirectory.CreateDevice(fullPath, kind, id, createIntermediateDirectories); - } - - public UnixSymbolicLink CreateSymbolicLink(string fullPath, string target, bool createIntermediateDirectories = true) - { - ValidateFullPath(fullPath); - return RootDirectory.CreateSymbolicLink(fullPath, target, createIntermediateDirectories); - } - - public TEntry CreateHardLink(string fullPath, TEntry entry, bool createIntermediateDirectories = true) where TEntry : UnixFileSystemEntry - { - ValidateFullPath(fullPath); - return RootDirectory.CreateHardLink(fullPath, entry, createIntermediateDirectories); - } - - public bool TryGetEntry(string fullPath, [NotNullWhen(true)] out UnixFileSystemEntry? entry) - { - ValidateFullPath(fullPath); - return RootDirectory.TryGetEntry(fullPath, out entry); - } - - public UnixFileSystemEntry GetEntry(string fullPath) - { - ValidateFullPath(fullPath); - return RootDirectory.GetEntry(fullPath); - } - - public void DeleteEntry(string fullPath) - { - ValidateFullPath(fullPath); - RootDirectory.DeleteEntry(fullPath); - } - - public void CopyEntry(string sourcePath, string destinationPath, UnixCopyMode mode = UnixCopyMode.Single) - { - ValidateFullPath(sourcePath, nameof(sourcePath)); - ValidateFullPath(destinationPath, nameof(destinationPath)); - RootDirectory.CopyEntry(sourcePath, destinationPath, mode); - } - - public void MoveEntry(string sourcePath, string destinationPath, bool createIntermediateDirectories = false) - { - ValidateFullPath(sourcePath, nameof(sourcePath)); - ValidateFullPath(destinationPath, nameof(destinationPath)); - RootDirectory.MoveEntry(sourcePath, destinationPath, createIntermediateDirectories); - } - - private static void ValidateFullPath(string fullPath, string? paramName = "fullPath") - { - ArgumentNullException.ThrowIfNull(fullPath); - if (fullPath.Length == 0) throw new ArgumentException("The path cannot be empty", paramName); - if (!UnixPath.IsPathRooted(fullPath)) throw new ArgumentException("The path must be absolute and start with a `/`", paramName); - } -} \ No newline at end of file diff --git a/src/XenoAtom.UnixTools/UnixMemoryFileSystemExtensions.cs b/src/XenoAtom.UnixTools/UnixMemoryFileSystemExtensions.cs index 5b27ec9..96ba09c 100644 --- a/src/XenoAtom.UnixTools/UnixMemoryFileSystemExtensions.cs +++ b/src/XenoAtom.UnixTools/UnixMemoryFileSystemExtensions.cs @@ -8,10 +8,24 @@ namespace XenoAtom.UnixTools; +/// +/// Extensions for . +/// public static class UnixMemoryFileSystemExtensions { - public static void ReadFrom(this UnixMemoryFileSystem fs, CpioReader reader) => fs.RootDirectory.ReadFrom(reader); - + /// + /// Reads the content of a CPIO archive into this file system. + /// + /// The filesystem to read the archive. + /// The CPIO reader. + public static void ReadFrom(this UnixInMemoryFileSystem fs, CpioReader reader) => fs.RootDirectory.ReadFrom(reader); + + /// + /// Reads the content of a CPIO archive into this directory. + /// + /// The root directory to read the archive. + /// The CPIO reader. + /// A boolean indicating whether existing entries should be overwritten. public static void ReadFrom(this UnixDirectory rootDirectory, CpioReader reader, bool overwrite = false) { rootDirectory.VerifyAttached(); @@ -91,8 +105,18 @@ public static void ReadFrom(this UnixDirectory rootDirectory, CpioReader reader, } } - public static void WriteTo(this UnixMemoryFileSystem fs, CpioWriter writer) => fs.RootDirectory.WriteTo(writer); - + /// + /// Writes the content of this file system to a CPIO archive. + /// + /// The filesystem to create the archive from. + /// The CPIO writer receiving the archive. + public static void WriteTo(this UnixInMemoryFileSystem fs, CpioWriter writer) => fs.RootDirectory.WriteTo(writer); + + /// + /// Writes the content of this directory to a CPIO archive. + /// + /// The root directory to create the archive from. + /// The CPIO writer receiving the archive. public static void WriteTo(this UnixDirectory rootDirectory, CpioWriter writer) { rootDirectory.VerifyAttached(); diff --git a/src/XenoAtom.UnixTools/UnixPath.cs b/src/XenoAtom.UnixTools/UnixPath.cs index 1b93da4..4446d16 100644 --- a/src/XenoAtom.UnixTools/UnixPath.cs +++ b/src/XenoAtom.UnixTools/UnixPath.cs @@ -8,11 +8,29 @@ namespace XenoAtom.UnixTools; +/// +/// Unix path utilities, similar to but with a unique support for Unix paths, the directory separator is always '/'. +/// public static class UnixPath { + /// + /// Default directory separator character. + /// public const char DirectorySeparatorChar = '/'; + + /// + /// Default directory separator character as a string. + /// public const string DirectorySeparatorCharAsString = "/"; + /// + /// Normalizes the given path. + /// + /// The path to normalize. + /// The normalized path + /// + /// This method will remove any relative segments (./, ../) from the path. + /// public static string Normalize(string path) { Validate(path); @@ -29,38 +47,34 @@ public static ReadOnlySpan GetDirectoryName(ReadOnlySpan path) return directoryNameOffset < 0 ? ReadOnlySpan.Empty : path.Slice(0, directoryNameOffset); } - internal static int GetDirectoryNameOffset(ReadOnlySpan path) - { - int end = path.Length; - if (end <= 0) - return -1; - - while (end > 0 && !IsDirectorySeparator(path[--end])) - { - } - - // Trim off any remaining separators (to deal with C:\foo\\bar) - while (end > 0 && IsDirectorySeparator(path[end - 1])) - { - end--; - } - - return end; - } - + /// + /// Gets the file name and extension for the specified path represented by a character span. + /// + /// The path to retrieve the file name and extension from. + /// The characters at the end of the path that represents the file name and extension. public static ReadOnlySpan GetFileName(ReadOnlySpan path) { int index = path.LastIndexOf('/'); return index < 0 ? path : path.Slice(index + 1); } - + + /// + /// Gets the file name without the extension for the specified path represented by a character span. + /// + /// The path to retrieve the file name without the extension. + /// The characters at the end of the path that represents the file name without the extension. public static ReadOnlySpan GetFileNameWithoutExtension(ReadOnlySpan path) { ReadOnlySpan fileName = GetFileName(path); int length = fileName.LastIndexOf('.'); return length < 0 ? fileName : fileName.Slice(0, length); } - + + /// + /// Gets the extension for the specified path represented by a character span. + /// + /// The path to retrieve the extension from. + /// The characters at the end of the path that represents the extension. public static ReadOnlySpan GetExtension(ReadOnlySpan path) { int length = path.Length; @@ -75,8 +89,20 @@ public static ReadOnlySpan GetExtension(ReadOnlySpan path) return ReadOnlySpan.Empty; } + /// + /// Validates the given Unix path. + /// + /// The path to validate. + /// + /// This method will throw an if the path is null or contains invalid null characters. + /// public static void Validate(string path) => Validate(path, nameof(path)); + /// + /// Validates the given Unix path. + /// + /// The path to validate. + /// The name of the parameter in case of throwing an exception. public static void Validate(string path, string paramName) { ArgumentNullException.ThrowIfNull(path, paramName); @@ -85,6 +111,11 @@ public static void Validate(string path, string paramName) throw new ArgumentException("Invalid null char found in path", paramName); } + /// + /// Validates the given Unix path. + /// + /// The path to validate. + /// The name of the parameter in case of throwing an exception. public static void Validate(ReadOnlySpan path, string paramName) { if (ContainsInvalidCharacters(path)) @@ -97,10 +128,21 @@ public static void Validate(ReadOnlySpan path, string paramName) /// The path to check /// True if the path contains valid characters. public static bool ContainsInvalidCharacters(ReadOnlySpan path) => path.IndexOf('\0') >= 0; - + + /// + /// Checks if a character is the directory '/' separator. + /// + /// The character to check. + /// True if the character is the directory separator. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsDirectorySeparator(char c) => c == DirectorySeparatorChar; + /// + /// Combines two paths. + /// + /// The first path to combine. + /// The second path to combine. + /// The combined path public static string Combine(string path1, string path2) { Validate(path1); @@ -191,9 +233,13 @@ public static string Combine(ReadOnlySpan paths) return builder.ToString(); } + - - + /// + /// Checks if the path is absolute/rooted. + /// + /// The path to check. + /// True if the path is absolute/rooted. public static bool IsPathRooted(ReadOnlySpan path) => path.Length > 0 && path[0] == '/'; internal static string NormalizeInternal(string path) @@ -325,6 +371,12 @@ internal static bool RemoveRelativeSegments(ReadOnlySpan path, ref ValueSt return true; } + /// + /// Gets the relative path from a source path to a child path. + /// + /// The source path + /// The child full path + /// The relative path from the source path to the child path public static string GetRelativePath(string sourcePath, string childFullPath) { Validate(sourcePath, nameof(sourcePath)); @@ -406,4 +458,24 @@ public static string GetRelativePath(string sourcePath, string childFullPath) return sb.ToString(); } + + internal static int GetDirectoryNameOffset(ReadOnlySpan path) + { + int end = path.Length; + if (end <= 0) + return -1; + + while (end > 0 && !IsDirectorySeparator(path[--end])) + { + } + + // Trim off any remaining separators (to deal with C:\foo\\bar) + while (end > 0 && IsDirectorySeparator(path[end - 1])) + { + end--; + } + + return end; + } + } \ No newline at end of file diff --git a/src/XenoAtom.UnixTools/UnixSymbolicLink.cs b/src/XenoAtom.UnixTools/UnixSymbolicLink.cs index 5b25f28..1d24069 100644 --- a/src/XenoAtom.UnixTools/UnixSymbolicLink.cs +++ b/src/XenoAtom.UnixTools/UnixSymbolicLink.cs @@ -4,6 +4,9 @@ namespace XenoAtom.UnixTools; +/// +/// A Unix symbolic link. +/// public sealed class UnixSymbolicLink : UnixFileSystemEntry { /// @@ -18,14 +21,20 @@ internal UnixSymbolicLink(string name, UnixInode node) : base(name, node) Mode = DefaultMode; } + /// + /// Gets or sets the relative target path of the symbolic link. + /// public string Target { get => Inode.GetSymbolicLinkTarget(); set => Inode.SetSymbolicLinkTarget(value); } + /// + /// Gets the full path of the target of this symbolic link. + /// public string TargetFullPath { - get => Parent is null ? "" : UnixPath.Combine(Parent!.FullPath, Target); + get => IsAttached ? UnixPath.Combine(Parent!.FullPath, Target) : ""; } } \ No newline at end of file diff --git a/src/XenoAtom.UnixTools/XenoAtom.UnixTools.csproj b/src/XenoAtom.UnixTools/XenoAtom.UnixTools.csproj index dc60039..333bd65 100644 --- a/src/XenoAtom.UnixTools/XenoAtom.UnixTools.csproj +++ b/src/XenoAtom.UnixTools/XenoAtom.UnixTools.csproj @@ -24,6 +24,7 @@ true true snupkg + True