From 61e133b118c1f7869973ba95e0ae86d7956485e3 Mon Sep 17 00:00:00 2001 From: duckdoom4 <60387522+duckdoom4@users.noreply.github.com> Date: Wed, 18 Jan 2023 18:03:51 +0100 Subject: [PATCH] Improve VFS code --- .../Archives/ZipArchiveFileSystem.cs | 46 +++++-- .../VFS/FileSystems/IFileSystem.cs | 26 +++- .../VFS/FileSystems/LayeredFileSystem.cs | 41 ++++--- .../VFS/FileSystems/PhysicalFileSystem.cs | 58 +++++---- .../VFS/FileSystems/ReadOnlyFileSystem.cs | 20 +++- .../VFS/FileSystems/RelativeFileSystem.cs | 83 +++++++++++++ pkNX.Containers/VFS/Util/FileSystemEntity.cs | 40 ------- .../VFS/Util/FileSystemExtensions.cs | 21 +--- pkNX.Containers/VFS/Util/FileSystemPath.cs | 113 +++++++----------- pkNX.Containers/VFS/Util/IFileSystemEntity.cs | 31 +++++ pkNX.Containers/VFS/Util/VirtualDirectory.cs | 19 ++- pkNX.Containers/VFS/Util/VirtualFile.cs | 25 ++-- pkNX.Containers/VFS/VirtualFileSystem.cs | 81 +++++++++---- 13 files changed, 378 insertions(+), 226 deletions(-) create mode 100644 pkNX.Containers/VFS/FileSystems/RelativeFileSystem.cs delete mode 100644 pkNX.Containers/VFS/Util/FileSystemEntity.cs create mode 100644 pkNX.Containers/VFS/Util/IFileSystemEntity.cs diff --git a/pkNX.Containers/VFS/FileSystems/Archives/ZipArchiveFileSystem.cs b/pkNX.Containers/VFS/FileSystems/Archives/ZipArchiveFileSystem.cs index 2f5802ec..ac0f62d3 100644 --- a/pkNX.Containers/VFS/FileSystems/Archives/ZipArchiveFileSystem.cs +++ b/pkNX.Containers/VFS/FileSystems/Archives/ZipArchiveFileSystem.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -7,7 +8,7 @@ namespace pkNX.Containers.VFS; public class ZipArchiveFileSystem : IFileSystem { - public ZipArchive ZipArchive { get; private set; } + public ZipArchive ZipArchive { get; } public bool IsReadOnly => false; @@ -48,20 +49,51 @@ protected string ToEntryPath(FileSystemPath path) { return ZipArchive.GetEntry(ToEntryPath(path)); } - public IEnumerable GetEntities(FileSystemPath path) + + public IEnumerable GetEntityPaths(FileSystemPath path) { - return GetZipEntries().Select(ToPath).Where(path.IsParentOf) + return GetZipEntries() + .Select(ToPath) + .Where(path.IsParentOf) .Select(entryPath => entryPath.ParentPath == path - ? entryPath - : path.AppendDirectory(entryPath.RemoveParent(path).GetDirectorySegments().First())) - .Distinct() - .ToList(); + ? entryPath + : path.AppendDirectory(entryPath.MakeRelativeTo(path).GetDirectorySegments().First())) + .Distinct(); + } + + public IEnumerable GetDirectoryPaths(FileSystemPath path) + { + if (!path.IsDirectory) + throw new ArgumentException("This FileSystemPath is not a directory.", nameof(path)); + + return GetZipEntries() + .Select(ToPath) + .Where(p => path.IsParentOf(p) && p.IsDirectory) + .Select(entryPath => entryPath.ParentPath == path + ? entryPath + : path.AppendDirectory(entryPath.MakeRelativeTo(path).GetDirectorySegments().First())) + .Distinct(); + } + + public IEnumerable GetFilePaths(FileSystemPath path) + { + if (!path.IsDirectory) + throw new ArgumentException("The specified path is not a directory.", nameof(path)); + + return GetZipEntries() + .Select(ToPath) + .Where(p => path.IsParentOf(p) && p.IsFile) + .Select(entryPath => entryPath.ParentPath == path + ? entryPath + : path.AppendDirectory(entryPath.MakeRelativeTo(path).GetDirectorySegments().First())) + .Distinct(); } public bool Exists(FileSystemPath path) { if (path.IsFile) return ToEntry(path) != null; + return GetZipEntries() .Select(ToPath) .Any(entryPath => entryPath.IsChildOf(path) || entryPath.Equals(path)); diff --git a/pkNX.Containers/VFS/FileSystems/IFileSystem.cs b/pkNX.Containers/VFS/FileSystems/IFileSystem.cs index fd2ad5c7..33050b3d 100644 --- a/pkNX.Containers/VFS/FileSystems/IFileSystem.cs +++ b/pkNX.Containers/VFS/FileSystems/IFileSystem.cs @@ -1,12 +1,16 @@ using System.IO; using System.Collections.Generic; using System; +using System.Linq; namespace pkNX.Containers.VFS; public interface IFileSystem : IDisposable { - IEnumerable GetEntities(FileSystemPath path); + IEnumerable GetEntityPaths(FileSystemPath path); + IEnumerable GetDirectoryPaths(FileSystemPath path); + IEnumerable GetFilePaths(FileSystemPath path); + bool Exists(FileSystemPath path); Stream CreateFile(FileSystemPath path); Stream OpenFile(FileSystemPath path, FileAccess access); @@ -41,4 +45,24 @@ public void WriteAllText(FileSystemPath path, string content) public static class IFileSystemExtensions { public static ReadOnlyFileSystem AsReadOnlyFileSystem(this IFileSystem self) => new(self); + + public static RelativeFileSystem AsRelativeFileSystem(this IFileSystem self, PathTransformation toAbsolutePath, PathTransformation toRelativePath) + { + return new(self, toAbsolutePath, toRelativePath); + } + + public static IEnumerable GetEntities(this IFileSystem self, FileSystemPath path) + { + return self.GetEntityPaths(path).Select(p => IFileSystemEntity.Create(self, p)); + } + + public static IEnumerable GetDirectories(this IFileSystem self, FileSystemPath path) + { + return self.GetDirectoryPaths(path).Select(p => VirtualDirectory.Create(self, p)); + } + + public static IEnumerable GetFiles(this IFileSystem self, FileSystemPath path) + { + return self.GetFilePaths(path).Select(p => VirtualFile.Create(self, p)); + } } diff --git a/pkNX.Containers/VFS/FileSystems/LayeredFileSystem.cs b/pkNX.Containers/VFS/FileSystems/LayeredFileSystem.cs index c24fc0df..17f32fdb 100644 --- a/pkNX.Containers/VFS/FileSystems/LayeredFileSystem.cs +++ b/pkNX.Containers/VFS/FileSystems/LayeredFileSystem.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; namespace pkNX.Containers.VFS; @@ -9,15 +10,15 @@ namespace pkNX.Containers.VFS; public class LayeredFileSystem : IFileSystem { public IEnumerable FileSystems { get; } + public LayeredFileSystem(IEnumerable fileSystems) { - FileSystems = fileSystems.ToArray(); + FileSystems = fileSystems; } - public LayeredFileSystem(params IFileSystem[] fileSystems) - { - FileSystems = fileSystems.ToArray(); - } + public LayeredFileSystem(params IFileSystem[] fileSystems) : + this(fileSystems.AsEnumerable()) + { } public void Dispose() { @@ -27,18 +28,28 @@ public void Dispose() GC.SuppressFinalize(this); } - public IEnumerable GetEntities(FileSystemPath path) + public IEnumerable GetEntityPaths(FileSystemPath path) { - var entities = new SortedList(); + var entities = new HashSet(); foreach (var fs in FileSystems.Where(fs => fs.Exists(path))) - { - foreach (var entity in fs.GetEntities(path)) - { - if (!entities.ContainsKey(entity)) - entities.Add(entity, entity); - } - } - return entities.Values; + entities.UnionWith(fs.GetEntityPaths(path)); + return entities; + } + + public IEnumerable GetDirectoryPaths(FileSystemPath path) + { + var directories = new HashSet(); + foreach (var fs in FileSystems.Where(fs => fs.Exists(path))) + directories.UnionWith(fs.GetDirectoryPaths(path)); + return directories; + } + + public IEnumerable GetFilePaths(FileSystemPath path) + { + var files = new HashSet(); + foreach (var fs in FileSystems.Where(fs => fs.Exists(path))) + files.UnionWith(fs.GetFilePaths(path)); + return files; } public bool Exists(FileSystemPath path) diff --git a/pkNX.Containers/VFS/FileSystems/PhysicalFileSystem.cs b/pkNX.Containers/VFS/FileSystems/PhysicalFileSystem.cs index 80471d13..d622b361 100644 --- a/pkNX.Containers/VFS/FileSystems/PhysicalFileSystem.cs +++ b/pkNX.Containers/VFS/FileSystems/PhysicalFileSystem.cs @@ -7,28 +7,27 @@ namespace pkNX.Containers.VFS; public class PhysicalFileSystem : IFileSystem { - #region Internals public string PhysicalRoot { get; } public PhysicalFileSystem(string physicalRoot) { - if (!Path.IsPathRooted(physicalRoot)) - physicalRoot = Path.GetFullPath(physicalRoot); - if (physicalRoot[^1] != Path.DirectorySeparatorChar) + physicalRoot = Path.GetFullPath(physicalRoot); + if (!physicalRoot.EndsWith(Path.DirectorySeparatorChar)) physicalRoot += Path.DirectorySeparatorChar; PhysicalRoot = physicalRoot; } public string GetPhysicalPath(FileSystemPath path) { - return Path.Combine(PhysicalRoot, path.ToString().Remove(0, 1).Replace(FileSystemPath.DirectorySeparator, Path.DirectorySeparatorChar)); + return Path.GetFullPath(PhysicalRoot + path); } public FileSystemPath GetVirtualFilePath(string physicalPath) { if (!physicalPath.StartsWith(PhysicalRoot, StringComparison.InvariantCultureIgnoreCase)) throw new ArgumentException("The specified path is not member of the PhysicalRoot.", nameof(physicalPath)); - string virtualPath = FileSystemPath.DirectorySeparator + physicalPath.Remove(0, PhysicalRoot.Length).Replace(Path.DirectorySeparatorChar, FileSystemPath.DirectorySeparator); + + string virtualPath = FileSystemPath.DirectorySeparator + physicalPath[PhysicalRoot.Length..].Replace(Path.DirectorySeparatorChar, FileSystemPath.DirectorySeparator); return FileSystemPath.Parse(virtualPath); } @@ -36,56 +35,69 @@ public FileSystemPath GetVirtualDirectoryPath(string physicalPath) { if (!physicalPath.StartsWith(PhysicalRoot, StringComparison.InvariantCultureIgnoreCase)) throw new ArgumentException("The specified path is not member of the PhysicalRoot.", nameof(physicalPath)); - string virtualPath = FileSystemPath.DirectorySeparator + physicalPath.Remove(0, PhysicalRoot.Length).Replace(Path.DirectorySeparatorChar, FileSystemPath.DirectorySeparator); - if (virtualPath[^1] != FileSystemPath.DirectorySeparator) + + string virtualPath = FileSystemPath.DirectorySeparator + physicalPath[PhysicalRoot.Length..].Replace(Path.DirectorySeparatorChar, FileSystemPath.DirectorySeparator); + if (!virtualPath.EndsWith(FileSystemPath.DirectorySeparator)) virtualPath += FileSystemPath.DirectorySeparator; return FileSystemPath.Parse(virtualPath); } - #endregion - - public IEnumerable GetEntities(FileSystemPath path) + public IEnumerable GetEntityPaths(FileSystemPath path) { - string physicalPath = GetPhysicalPath(path); - string[] directories = System.IO.Directory.GetDirectories(physicalPath); - string[] files = System.IO.Directory.GetFiles(physicalPath); - var virtualDirectories = directories.Select(GetVirtualDirectoryPath); - var virtualFiles = files.Select(GetVirtualFilePath); - return virtualDirectories.Concat(virtualFiles); + return GetDirectoryPaths(path).Concat(GetFilePaths(path)); + } + + public IEnumerable GetDirectoryPaths(FileSystemPath path) + { + if (!path.IsDirectory) + throw new ArgumentException("This FileSystemPath is not a directory.", nameof(path)); + + var physicalPaths = Directory.GetDirectories(GetPhysicalPath(path)); + return physicalPaths.Select(GetVirtualDirectoryPath); + } + + public IEnumerable GetFilePaths(FileSystemPath path) + { + if (!path.IsDirectory) + throw new ArgumentException("The specified path is not a directory.", nameof(path)); + + var physicalPaths = Directory.GetFiles(GetPhysicalPath(path)); + return physicalPaths.Select(GetVirtualFilePath); } public bool Exists(FileSystemPath path) { - return path.IsFile ? System.IO.File.Exists(GetPhysicalPath(path)) : System.IO.Directory.Exists(GetPhysicalPath(path)); + var fullPath = GetPhysicalPath(path); + return path.IsFile ? File.Exists(fullPath) : Directory.Exists(fullPath); } public Stream CreateFile(FileSystemPath path) { if (!path.IsFile) throw new ArgumentException("The specified path is not a file.", nameof(path)); - return System.IO.File.Create(GetPhysicalPath(path)); + return File.Create(GetPhysicalPath(path)); } public Stream OpenFile(FileSystemPath path, FileAccess access) { if (!path.IsFile) throw new ArgumentException("The specified path is not a file.", nameof(path)); - return System.IO.File.Open(GetPhysicalPath(path), FileMode.Open, access); + return File.Open(GetPhysicalPath(path), FileMode.Open, access); } public void CreateDirectory(FileSystemPath path) { if (!path.IsDirectory) throw new ArgumentException("The specified path is not a directory.", nameof(path)); - System.IO.Directory.CreateDirectory(GetPhysicalPath(path)); + Directory.CreateDirectory(GetPhysicalPath(path)); } public void Delete(FileSystemPath path) { if (path.IsFile) - System.IO.File.Delete(GetPhysicalPath(path)); + File.Delete(GetPhysicalPath(path)); else - System.IO.Directory.Delete(GetPhysicalPath(path), true); + Directory.Delete(GetPhysicalPath(path), true); } public void Dispose() diff --git a/pkNX.Containers/VFS/FileSystems/ReadOnlyFileSystem.cs b/pkNX.Containers/VFS/FileSystems/ReadOnlyFileSystem.cs index 4b2c35a3..80f6dbf1 100644 --- a/pkNX.Containers/VFS/FileSystems/ReadOnlyFileSystem.cs +++ b/pkNX.Containers/VFS/FileSystems/ReadOnlyFileSystem.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; namespace pkNX.Containers.VFS; @@ -21,16 +22,31 @@ public void Dispose() GC.SuppressFinalize(this); } - public IEnumerable GetEntities(FileSystemPath path) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IEnumerable GetEntityPaths(FileSystemPath path) { - return FileSystem.GetEntities(path); + return FileSystem.GetEntityPaths(path); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IEnumerable GetDirectoryPaths(FileSystemPath path) + { + return FileSystem.GetDirectoryPaths(path); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IEnumerable GetFilePaths(FileSystemPath path) + { + return FileSystem.GetFilePaths(path); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Exists(FileSystemPath path) { return FileSystem.Exists(path); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Stream OpenFile(FileSystemPath path, FileAccess access) { if (access != FileAccess.Read) diff --git a/pkNX.Containers/VFS/FileSystems/RelativeFileSystem.cs b/pkNX.Containers/VFS/FileSystems/RelativeFileSystem.cs new file mode 100644 index 00000000..c02fc677 --- /dev/null +++ b/pkNX.Containers/VFS/FileSystems/RelativeFileSystem.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace pkNX.Containers.VFS; + +public delegate FileSystemPath PathTransformation(FileSystemPath arg); + +public class RelativeFileSystem : IFileSystem +{ + public IFileSystem FileSystem { get; } + public bool IsReadOnly => FileSystem.IsReadOnly; + + public PathTransformation ToAbsolutePath { get; } + public PathTransformation ToRelativePath { get; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RelativeFileSystem(IFileSystem fileSystem, PathTransformation toAbsolutePath, PathTransformation toRelativePath) + { + FileSystem = fileSystem; + ToAbsolutePath = toAbsolutePath; + ToRelativePath = toRelativePath; + } + + public void Dispose() + { + FileSystem.Dispose(); + GC.SuppressFinalize(this); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IEnumerable GetEntityPaths(FileSystemPath path) + { + return FileSystem.GetEntityPaths(ToAbsolutePath(path)) + .Select(p => ToRelativePath(p)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IEnumerable GetDirectoryPaths(FileSystemPath path) + { + return FileSystem.GetDirectoryPaths(ToAbsolutePath(path)) + .Select(p => ToRelativePath(p)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IEnumerable GetFilePaths(FileSystemPath path) + { + return FileSystem.GetFilePaths(ToAbsolutePath(path)) + .Select(p => ToRelativePath(p)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Exists(FileSystemPath path) + { + return FileSystem.Exists(ToAbsolutePath(path)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Stream OpenFile(FileSystemPath path, FileAccess access) + { + return FileSystem.OpenFile(ToAbsolutePath(path), access); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Stream CreateFile(FileSystemPath path) + { + return FileSystem.CreateFile(ToAbsolutePath(path)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CreateDirectory(FileSystemPath path) + { + FileSystem.CreateDirectory(ToAbsolutePath(path)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Delete(FileSystemPath path) + { + FileSystem.Delete(ToAbsolutePath(path)); + } +} diff --git a/pkNX.Containers/VFS/Util/FileSystemEntity.cs b/pkNX.Containers/VFS/Util/FileSystemEntity.cs deleted file mode 100644 index c3ace916..00000000 --- a/pkNX.Containers/VFS/Util/FileSystemEntity.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace pkNX.Containers.VFS; - -public class FileSystemEntity : IEquatable -{ - public IFileSystem FileSystem { get; } - public FileSystemPath Path { get; } - public string Name => Path.EntityName; - - public FileSystemEntity(IFileSystem fileSystem, FileSystemPath path) - { - FileSystem = fileSystem; - Path = path; - } - - public override bool Equals(object? obj) - { - return obj is FileSystemEntity other && ((IEquatable)this).Equals(other); - } - - public override int GetHashCode() - { - return FileSystem.GetHashCode() ^ Path.GetHashCode(); - } - - bool IEquatable.Equals(FileSystemEntity? other) - { - return FileSystem.Equals(other?.FileSystem) && Path.Equals(other.Path); - } - - public static FileSystemEntity Create(IFileSystem fileSystem, FileSystemPath path) - { - if (path.IsFile) - return new VirtualFile(fileSystem, path); - - return new VirtualDirectory(fileSystem, path); - } -} - diff --git a/pkNX.Containers/VFS/Util/FileSystemExtensions.cs b/pkNX.Containers/VFS/Util/FileSystemExtensions.cs index 9f280787..e2267be9 100644 --- a/pkNX.Containers/VFS/Util/FileSystemExtensions.cs +++ b/pkNX.Containers/VFS/Util/FileSystemExtensions.cs @@ -7,32 +7,17 @@ namespace pkNX.Containers.VFS; public static class FileSystemExtensions { - public static Stream Open(this VirtualFile file, FileAccess access) - { - return file.FileSystem.OpenFile(file.Path, access); - } - - public static void Delete(this FileSystemEntity entity) - { - entity.FileSystem.Delete(entity.Path); - } - public static IEnumerable GetEntityPaths(this VirtualDirectory directory) { - return directory.FileSystem.GetEntities(directory.Path); - } - - public static IEnumerable GetEntities(this VirtualDirectory directory) - { - var paths = directory.GetEntityPaths(); - return paths.Select(p => FileSystemEntity.Create(directory.FileSystem, p)); + return directory.FileSystem.GetEntityPaths(directory.Path); } public static IEnumerable GetEntitiesRecursive(this IFileSystem fileSystem, FileSystemPath path) { if (!path.IsDirectory) throw new ArgumentException("The specified path is not a directory."); - foreach (var entity in fileSystem.GetEntities(path)) + + foreach (var entity in fileSystem.GetEntityPaths(path)) { yield return entity; diff --git a/pkNX.Containers/VFS/Util/FileSystemPath.cs b/pkNX.Containers/VFS/Util/FileSystemPath.cs index cc8c88c3..ed0c5d5e 100644 --- a/pkNX.Containers/VFS/Util/FileSystemPath.cs +++ b/pkNX.Containers/VFS/Util/FileSystemPath.cs @@ -6,31 +6,30 @@ namespace pkNX.Containers.VFS; -public readonly struct FileSystemPath : IEquatable, IComparable +public readonly record struct FileSystemPath : IComparable { public const char DirectorySeparator = '/'; - public static FileSystemPath Root { get; } + public static FileSystemPath Root { get; } = new(DirectorySeparator.ToString()); - public string Path { get; } = "/"; - - public bool IsDirectory => Path[^1] == DirectorySeparator; + public string Path { get; } + public bool IsDirectory => Path.EndsWith(DirectorySeparator); public bool IsFile => !IsDirectory; - public bool IsRoot => Path.Length == 1; public string EntityName { get { - string name = Path; if (IsRoot) return string.Empty; - int endOfName = name.Length; + + int endOfName = Path.Length; if (IsDirectory) - endOfName--; - int startOfName = name.LastIndexOf(DirectorySeparator, endOfName - 1, endOfName) + 1; - return name[startOfName..endOfName]; + --endOfName; + + int startOfName = Path.LastIndexOf(DirectorySeparator, endOfName - 1, endOfName) + 1; + return Path[startOfName..endOfName]; } } @@ -38,22 +37,16 @@ public FileSystemPath ParentPath { get { - string parentPath = Path; if (IsRoot) throw new InvalidOperationException("There is no parent of root."); - int lookaheadCount = parentPath.Length; - if (IsDirectory) - lookaheadCount--; - int index = parentPath.LastIndexOf(DirectorySeparator, lookaheadCount - 1, lookaheadCount); - Debug.Assert(index >= 0); - parentPath = parentPath.Remove(index + 1); - return new FileSystemPath(parentPath); - } - } - static FileSystemPath() - { - Root = new FileSystemPath(DirectorySeparator.ToString()); + int endOfPath = Path.Length; + if (IsDirectory) + --endOfPath; + + endOfPath = Path.LastIndexOf(DirectorySeparator, endOfPath - 1, endOfPath) + 1; + return new(Path[..endOfPath]); + } } private FileSystemPath(string path) @@ -63,8 +56,7 @@ private FileSystemPath(string path) public static implicit operator FileSystemPath(string path) { - var parsed = FileSystemPath.Parse(path); - return parsed; + return Parse(path); } public static implicit operator string(FileSystemPath path) @@ -74,9 +66,7 @@ private FileSystemPath(string path) public static bool IsRooted(string s) { - if (s.Length == 0) - return false; - return s[0] == DirectorySeparator; + return s.StartsWith(DirectorySeparator); } public static FileSystemPath Parse(string s) @@ -87,24 +77,25 @@ public static FileSystemPath Parse(string s) throw new UriFormatException($"Could not parse input \"{s}\": Path is not rooted."); if (s.Contains(string.Concat(DirectorySeparator, DirectorySeparator))) throw new UriFormatException($"Could not parse input \"{s}\": Path contains double directory-separators."); - return new FileSystemPath(s); + return new(s); } - public FileSystemPath AppendPath(string relativePath) + [Pure] + public FileSystemPath AppendPath(string strPath) { - if (IsRooted(relativePath)) - throw new ArgumentException("The specified path should be relative.", nameof(relativePath)); if (!IsDirectory) throw new InvalidOperationException("This FileSystemPath is not a directory."); - return new FileSystemPath(Path + relativePath); + + if (IsRooted(strPath)) + throw new ArgumentException("The specified path is a rooted path.", nameof(strPath)); + + return new(Path + strPath); } [Pure] public FileSystemPath AppendPath(FileSystemPath path) { - if (!IsDirectory) - throw new InvalidOperationException("This FileSystemPath is not a directory."); - return new FileSystemPath(Path + path.Path[1..]); + return AppendPath(path.Path[1..]); } [Pure] @@ -130,7 +121,10 @@ public FileSystemPath AppendFile(string fileName) [Pure] public bool IsParentOf(FileSystemPath path) { - return IsDirectory && Path.Length != path.Path.Length && path.Path.StartsWith(Path); + if (!IsDirectory) + throw new ArgumentException($"Path \"{Path}\" can not be a parent: it is not a directory."); + + return Path.Length != path.Path.Length && path.Path.StartsWith(Path); } [Pure] @@ -140,13 +134,16 @@ public bool IsChildOf(FileSystemPath path) } [Pure] - public FileSystemPath RemoveParent(FileSystemPath parent) + public FileSystemPath MakeRelativeTo(FileSystemPath parent) { - if (!parent.IsDirectory) - throw new ArgumentException("The specified path can not be the parent of this path: it is not a directory."); - if (!Path.StartsWith(parent.Path)) - throw new ArgumentException("The specified path is not a parent of this path."); - return new FileSystemPath(Path.Remove(0, parent.Path.Length - 1)); + if (Path == parent.Path) + return Root; + + if (!IsChildOf(parent)) + throw new ArgumentException($"Path \"{parent}\" is not a parent of \"{Path}\"."); + + int parentPathEnd = parent.Path.Length - 1; + return new(Path[parentPathEnd..]); } [Pure] @@ -205,32 +202,4 @@ public override string ToString() { return Path; } - - [Pure] - public override bool Equals(object? obj) - { - return obj is FileSystemPath path && Equals(path); - } - - [Pure] - public bool Equals(FileSystemPath other) - { - return other.Path.Equals(Path); - } - - [Pure] - public override int GetHashCode() - { - return Path.GetHashCode(); - } - - public static bool operator ==(FileSystemPath pathA, FileSystemPath pathB) - { - return pathA.Equals(pathB); - } - - public static bool operator !=(FileSystemPath pathA, FileSystemPath pathB) - { - return !(pathA == pathB); - } } diff --git a/pkNX.Containers/VFS/Util/IFileSystemEntity.cs b/pkNX.Containers/VFS/Util/IFileSystemEntity.cs new file mode 100644 index 00000000..07c646dc --- /dev/null +++ b/pkNX.Containers/VFS/Util/IFileSystemEntity.cs @@ -0,0 +1,31 @@ +namespace pkNX.Containers.VFS; + +public interface IFileSystemEntity +{ + IFileSystem FileSystem { get; } + FileSystemPath Path { get; } + string Name { get; } + VirtualDirectory ParentDirectory { get; } + + internal static IFileSystemEntity Create(IFileSystem fileSystem, FileSystemPath path) + { + if (path.IsFile) + return VirtualFile.Create(fileSystem, path); + + return VirtualDirectory.Create(fileSystem, path); + } +} + +public static class IFileSystemEntityExtensions +{ + public static void Delete(this IFileSystemEntity e) + { + e.FileSystem.Delete(e.Path); + } + + public static void Exists(this IFileSystemEntity e) + { + e.FileSystem.Exists(e.Path); + } +} + diff --git a/pkNX.Containers/VFS/Util/VirtualDirectory.cs b/pkNX.Containers/VFS/Util/VirtualDirectory.cs index b813f0ae..3c13c8db 100644 --- a/pkNX.Containers/VFS/Util/VirtualDirectory.cs +++ b/pkNX.Containers/VFS/Util/VirtualDirectory.cs @@ -1,22 +1,19 @@ using System; +using System.Collections.Generic; namespace pkNX.Containers.VFS; -public class VirtualDirectory : FileSystemEntity, IEquatable +public readonly record struct VirtualDirectory(IFileSystem FileSystem, FileSystemPath Path) : IFileSystemEntity { - public VirtualDirectory(IFileSystem fileSystem, FileSystemPath path) : base(fileSystem, path) + public string Name => Path.EntityName; + public VirtualDirectory ParentDirectory => Create(FileSystem, Path.ParentPath); + + + internal static VirtualDirectory Create(IFileSystem fileSystem, FileSystemPath path) { if (!path.IsDirectory) throw new ArgumentException("The specified path is no directory.", nameof(path)); - } - public bool Equals(VirtualDirectory? other) - { - return ((IEquatable)this).Equals(other); - } - - public override bool Equals(object? obj) - { - return Equals(obj as VirtualDirectory); + return new VirtualDirectory(fileSystem, path); } } diff --git a/pkNX.Containers/VFS/Util/VirtualFile.cs b/pkNX.Containers/VFS/Util/VirtualFile.cs index 9f41bdc4..b24c9f3e 100644 --- a/pkNX.Containers/VFS/Util/VirtualFile.cs +++ b/pkNX.Containers/VFS/Util/VirtualFile.cs @@ -1,24 +1,23 @@ using System; +using System.IO; namespace pkNX.Containers.VFS; -public class VirtualFile : FileSystemEntity, IEquatable +public readonly record struct VirtualFile(IFileSystem FileSystem, FileSystemPath Path) : IFileSystemEntity { - public VirtualFile(IFileSystem fileSystem, FileSystemPath path) : - base(fileSystem, path) + public string Name => Path.EntityName; + public VirtualDirectory ParentDirectory => VirtualDirectory.Create(FileSystem, Path.ParentPath); + + public Stream Open(FileAccess access) + { + return FileSystem.OpenFile(Path, access); + } + + internal static VirtualFile Create(IFileSystem fileSystem, FileSystemPath path) { if (!path.IsFile) throw new ArgumentException("The specified path is no file.", nameof(path)); - } - public bool Equals(VirtualFile? other) - { - return ((IEquatable)this).Equals(other); - } - - public override bool Equals(object? obj) - { - return Equals(obj as VirtualFile); + return new VirtualFile(fileSystem, path); } } - diff --git a/pkNX.Containers/VFS/VirtualFileSystem.cs b/pkNX.Containers/VFS/VirtualFileSystem.cs index db86247b..60ab2193 100644 --- a/pkNX.Containers/VFS/VirtualFileSystem.cs +++ b/pkNX.Containers/VFS/VirtualFileSystem.cs @@ -2,22 +2,36 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; namespace pkNX.Containers.VFS; -public record MountPoint(FileSystemPath Path, IFileSystem FileSystem) : IComparable +public record MountPoint { - public int CompareTo(MountPoint? other) + public FileSystemPath MountPath { get; } + public IFileSystem FileSystem { get; } + + public FileSystemPath ToAbsolutePath(FileSystemPath path) { - return other?.Path.CompareTo(Path) ?? 1; + return MountPath.AppendPath(path); + } + + public FileSystemPath ToRelativePath(FileSystemPath path) + { + return path.IsRoot ? path : path.MakeRelativeTo(MountPath); + } + + public MountPoint(FileSystemPath mountPath, IFileSystem fileSystem) + { + MountPath = mountPath; + FileSystem = fileSystem.AsRelativeFileSystem(ToAbsolutePath, ToRelativePath); } } public class VirtualFileSystem : IFileSystem { - public bool IsReadOnly => Mounts.All(x => x.FileSystem.IsReadOnly); - public SortedSet Mounts { get; } + public bool IsReadOnly => Mounts.All(x => x.FileSystem.IsReadOnly); public VirtualFileSystem(IEnumerable mounts) { @@ -28,11 +42,6 @@ public VirtualFileSystem(IEnumerable mounts) this(mounts.AsEnumerable()) { } - protected MountPoint Get(FileSystemPath path) - { - return Mounts.First(pair => pair.Path == path || pair.Path.IsParentOf(path)); - } - public void Dispose() { foreach (var fs in Mounts.Select(x => x.FileSystem)) @@ -41,40 +50,64 @@ public void Dispose() GC.SuppressFinalize(this); } - public IEnumerable GetEntities(FileSystemPath path) + protected MountPoint GetMountPoint(FileSystemPath path) { - MountPoint point = Get(path); - IEnumerable entities = point.FileSystem.GetEntities(path.IsRoot ? path : path.RemoveParent(point.Path)); - return entities.Select(p => point.Path.AppendPath(p)); + return Mounts.First(mount => mount.MountPath == path || mount.MountPath.IsParentOf(path)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IEnumerable GetEntityPaths(FileSystemPath path) + { + var mount = GetMountPoint(path); + return mount.FileSystem.GetEntityPaths(path); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IEnumerable GetDirectoryPaths(FileSystemPath path) + { + var mount = GetMountPoint(path); + return mount.FileSystem.GetDirectoryPaths(path); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IEnumerable GetFilePaths(FileSystemPath path) + { + var mount = GetMountPoint(path); + return mount.FileSystem.GetFilePaths(path); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Exists(FileSystemPath path) { - var pair = Get(path); - return pair.FileSystem.Exists(path.RemoveParent(pair.Path)); + var mount = GetMountPoint(path); + return mount.FileSystem.Exists(path); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Stream CreateFile(FileSystemPath path) { - var pair = Get(path); - return pair.FileSystem.CreateFile(path.RemoveParent(pair.Path)); + var mount = GetMountPoint(path); + return mount.FileSystem.CreateFile(path); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Stream OpenFile(FileSystemPath path, FileAccess access) { - var pair = Get(path); - return pair.FileSystem.OpenFile(path.RemoveParent(pair.Path), access); + var mount = GetMountPoint(path); + return mount.FileSystem.OpenFile(path, access); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void CreateDirectory(FileSystemPath path) { - var pair = Get(path); - pair.FileSystem.CreateDirectory(path.RemoveParent(pair.Path)); + var mount = GetMountPoint(path); + mount.FileSystem.CreateDirectory(path); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Delete(FileSystemPath path) { - var pair = Get(path); - pair.FileSystem.Delete(path.RemoveParent(pair.Path)); + var mount = GetMountPoint(path); + mount.FileSystem.Delete(path); } }