mirror of
https://github.com/kwsch/pkNX.git
synced 2026-09-30 13:16:58 -05:00
Made VFS fully functional
Still need to start using it, but want to do it a bit slow to test it out. Also should really build some unit tests for this one..
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -9,15 +10,18 @@ namespace pkNX.Containers.VFS;
|
||||
|
||||
public class LayeredFileSystem : IFileSystem
|
||||
{
|
||||
public IEnumerable<IFileSystem> FileSystems { get; }
|
||||
public IReadOnlyList<IFileSystem> FileSystems { get; }
|
||||
|
||||
public LayeredFileSystem(IEnumerable<IFileSystem> fileSystems)
|
||||
public LayeredFileSystem(IReadOnlyList<IFileSystem> fileSystems)
|
||||
{
|
||||
FileSystems = fileSystems;
|
||||
|
||||
Debug.Assert(FileSystems.Any(), "No filesystems provided.");
|
||||
Debug.Assert(FileSystems.Any(fs => !fs.IsReadOnly), "Should contain at least one writable filesystem.");
|
||||
}
|
||||
|
||||
public LayeredFileSystem(params IFileSystem[] fileSystems) :
|
||||
this(fileSystems.AsEnumerable())
|
||||
this(fileSystems.AsReadOnly())
|
||||
{ }
|
||||
|
||||
public void Dispose()
|
||||
@@ -52,35 +56,157 @@ public IEnumerable<FileSystemPath> GetFilePaths(FileSystemPath path, Func<FileSy
|
||||
return files;
|
||||
}
|
||||
|
||||
public IFileSystem GetFirst()
|
||||
{
|
||||
return FileSystems.First();
|
||||
}
|
||||
|
||||
public IFileSystem GetFirstWritable()
|
||||
{
|
||||
return FileSystems.First(fs => !fs.IsReadOnly);
|
||||
}
|
||||
|
||||
public bool Exists(FileSystemPath path)
|
||||
{
|
||||
return FileSystems.Any(fs => fs.Exists(path));
|
||||
}
|
||||
|
||||
public IFileSystem? GetFirst(FileSystemPath path)
|
||||
public IFileSystem? GetFirstWhereExists(FileSystemPath path)
|
||||
{
|
||||
return FileSystems.FirstOrDefault(fs => fs.Exists(path));
|
||||
}
|
||||
|
||||
public Stream CreateFile(FileSystemPath path)
|
||||
public IFileSystem? GetFirstWritableWhereExists(FileSystemPath path)
|
||||
{
|
||||
IFileSystem fs = GetFirst(path) ?? FileSystems.First();
|
||||
return fs.CreateFile(path);
|
||||
return FileSystems.FirstOrDefault(fs => !fs.IsReadOnly && fs.Exists(path));
|
||||
}
|
||||
|
||||
public Stream OpenFile(FileSystemPath path, FileAccess access)
|
||||
private bool ValidateOpenMode(FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read)
|
||||
{
|
||||
IFileSystem? fs = GetFirst(path);
|
||||
if (fs == null)
|
||||
throw new FileNotFoundException($"Unable to find {path.Path}");
|
||||
return fs.OpenFile(path, access);
|
||||
if (!access.HasFlag(FileAccess.Write) && mode is FileMode.Create or FileMode.CreateNew or FileMode.Truncate or FileMode.Append)
|
||||
{
|
||||
throw new ArgumentException($"File mode '{mode}' requires files to be accessed with write permission, but the access mode was '{access}'", nameof(access));
|
||||
}
|
||||
|
||||
if (access.HasFlag(FileAccess.Read) && mode == FileMode.Append)
|
||||
{
|
||||
throw new ArgumentException("File mode 'Append' requires files to be accessed with in read/write permission.", nameof(access));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Stream OpenFile(FileSystemPath path, FileMode mode = FileMode.Open, FileAccess access = FileAccess.Read)
|
||||
{
|
||||
if (!ValidateOpenMode(mode, access))
|
||||
return Stream.Null;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case FileMode.Open:
|
||||
{
|
||||
// just read from top layer
|
||||
IFileSystem? fs = GetFirstWhereExists(path);
|
||||
if (fs == null)
|
||||
throw new FileNotFoundException($"Could not find the file at the specified path: {path}", nameof(path));
|
||||
|
||||
return fs.OpenFile(path, FileMode.Open, access);
|
||||
}
|
||||
case FileMode.OpenOrCreate:
|
||||
{
|
||||
// - Specifies that the operating system should open a file if it exists; otherwise, a new file should be created.
|
||||
// If the file is opened with FileAccess.Read, Read permission is required.
|
||||
// If the file access is FileAccess.Write, Write permission is required.
|
||||
// If the file is opened with FileAccess.ReadWrite, both Read and Write permissions are required.
|
||||
|
||||
IFileSystem? fs = GetFirstWhereExists(path);
|
||||
if (fs != null)
|
||||
{
|
||||
// The file exists, now we need to check if we can open it with the requested access
|
||||
// readonly fs requires special handling for write requests.
|
||||
// if readonly access is requested, we can open it if the fs is readonly
|
||||
var readStream = fs.OpenFile(path);
|
||||
if (!fs.IsReadOnly || access == FileAccess.Read)
|
||||
return readStream;
|
||||
|
||||
// For write-only access, we can just create a new empty file
|
||||
IFileSystem writableFs = GetFirstWritable();
|
||||
writableFs.CreateDirectoryRecursive(path.ParentPath);
|
||||
var writeStream = writableFs.CreateFile(path);
|
||||
|
||||
if (access == FileAccess.Write)
|
||||
return writeStream;
|
||||
|
||||
// For read-write access, we need to first copy the file to a writable fs
|
||||
readStream.CopyTo(writeStream);
|
||||
writeStream.Seek(0, SeekOrigin.Begin);
|
||||
readStream.Dispose();
|
||||
return writeStream;
|
||||
}
|
||||
|
||||
// The file does not exist, create it on the first writable fs
|
||||
return GetFirstWritable().CreateFile(path);
|
||||
}
|
||||
case FileMode.CreateNew:
|
||||
{
|
||||
// Specifies that the operating system should create a new file.
|
||||
// - Requires Write permission.
|
||||
// - Requires the file does not already exist.
|
||||
|
||||
if (Exists(path))
|
||||
throw new IOException($"File {path.Path} already exists.");
|
||||
|
||||
return GetFirstWritable().CreateFile(path);
|
||||
}
|
||||
case FileMode.Create:
|
||||
{
|
||||
// - This requires Write permission.
|
||||
// - FileMode.Create is equivalent to requesting that if the file does not exist, use CreateNew; otherwise, use Truncate.
|
||||
// Specifies that the operating system should create a new file.
|
||||
// If the file already exists, it will be overwritten.
|
||||
IFileSystem? fs = GetFirstWhereExists(path);
|
||||
if (fs != null)
|
||||
return fs.OpenFile(path, FileMode.Truncate, access);
|
||||
|
||||
return GetFirstWritable().CreateFile(path);
|
||||
}
|
||||
|
||||
case FileMode.Truncate:
|
||||
{
|
||||
// - Requires Write permission.
|
||||
// - Requires no Read access requested
|
||||
// - Requires existing file
|
||||
// When the file is opened, it should be truncated so that its size is zero bytes.
|
||||
|
||||
IFileSystem? fs = GetFirstWhereExists(path);
|
||||
if (fs == null)
|
||||
throw new FileNotFoundException($"Could not find the file at the specified path: {path}", nameof(path));
|
||||
|
||||
return fs.OpenFile(path, FileMode.Truncate, access);
|
||||
}
|
||||
case FileMode.Append:
|
||||
{
|
||||
// Opens the file if it exists and seeks to the end of the file, or creates a new file.
|
||||
// This requires R/W permission.
|
||||
// Trying to seek to a position before the end of the file throws an IOException exception, and any attempt to read fails and throws a NotSupportedException exception.
|
||||
|
||||
IFileSystem? fs = GetFirstWhereExists(path);
|
||||
if (fs == null)
|
||||
throw new FileNotFoundException($"Could not find the file at the specified path: {path}", nameof(path));
|
||||
|
||||
return fs.OpenFile(path, FileMode.Append, access);
|
||||
}
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(mode), mode, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateDirectory(FileSystemPath path)
|
||||
{
|
||||
if (Exists(path))
|
||||
throw new ArgumentException("The specified directory already exists.");
|
||||
IFileSystem? fs = GetFirst(path.ParentPath);
|
||||
IFileSystem? fs = GetFirstWhereExists(path.ParentPath);
|
||||
if (fs == null)
|
||||
throw new ArgumentException("The directory-parent does not exist.");
|
||||
fs.CreateDirectory(path);
|
||||
|
||||
Reference in New Issue
Block a user