mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Add InMemoryFileSystem
This commit is contained in:
parent
da467a10b0
commit
1ae973a346
791
src/LibHac/Fs/InMemoryFileSystem.cs
Normal file
791
src/LibHac/Fs/InMemoryFileSystem.cs
Normal file
@ -0,0 +1,791 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using LibHac.Common;
|
||||
using LibHac.FsSystem;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
/// <summary>
|
||||
/// A filesystem stored in-memory. Mainly used for testing.
|
||||
/// </summary>
|
||||
public class InMemoryFileSystem : AttributeFileSystemBase
|
||||
{
|
||||
private FileTable FsTable { get; }
|
||||
|
||||
public InMemoryFileSystem()
|
||||
{
|
||||
FsTable = new FileTable();
|
||||
}
|
||||
|
||||
protected override Result CreateDirectoryImpl(string path)
|
||||
{
|
||||
return FsTable.AddDirectory(new U8Span(path));
|
||||
}
|
||||
|
||||
protected override Result CreateDirectoryImpl(string path, NxFileAttributes archiveAttribute)
|
||||
{
|
||||
var u8Path = new U8Span(path);
|
||||
|
||||
Result rc = FsTable.AddDirectory(u8Path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsTable.GetDirectory(u8Path, out DirectoryNode dir);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
dir.Attributes = archiveAttribute;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result CreateFileImpl(string path, long size, CreateFileOptions options)
|
||||
{
|
||||
var u8Path = new U8Span(path);
|
||||
|
||||
Result rc = FsTable.AddFile(u8Path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsTable.GetFile(u8Path, out FileNode file);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return file.File.SetSize(size);
|
||||
}
|
||||
|
||||
protected override Result DeleteDirectoryImpl(string path)
|
||||
{
|
||||
return FsTable.DeleteDirectory(new U8Span(path), false);
|
||||
}
|
||||
|
||||
protected override Result DeleteDirectoryRecursivelyImpl(string path)
|
||||
{
|
||||
return FsTable.DeleteDirectory(new U8Span(path), true);
|
||||
}
|
||||
|
||||
protected override Result CleanDirectoryRecursivelyImpl(string path)
|
||||
{
|
||||
return FsTable.CleanDirectory(new U8Span(path));
|
||||
}
|
||||
|
||||
protected override Result DeleteFileImpl(string path)
|
||||
{
|
||||
return FsTable.DeleteFile(new U8Span(path));
|
||||
}
|
||||
|
||||
protected override Result OpenDirectoryImpl(out IDirectory directory, string path, OpenDirectoryMode mode)
|
||||
{
|
||||
directory = default;
|
||||
|
||||
Result rs = FsTable.GetDirectory(new U8Span(path), out DirectoryNode dirNode);
|
||||
if (rs.IsFailure()) return rs;
|
||||
|
||||
directory = new MemoryDirectory(dirNode, mode);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result OpenFileImpl(out IFile file, string path, OpenMode mode)
|
||||
{
|
||||
file = default;
|
||||
var u8Path = new U8Span(path);
|
||||
|
||||
Result rc = FsTable.GetFile(u8Path, out FileNode fileNode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
file = new MemoryFile(mode, fileNode.File);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result RenameDirectoryImpl(string oldPath, string newPath)
|
||||
{
|
||||
return FsTable.RenameDirectory(new U8Span(oldPath), new U8Span(newPath));
|
||||
}
|
||||
|
||||
protected override Result RenameFileImpl(string oldPath, string newPath)
|
||||
{
|
||||
return FsTable.RenameFile(new U8Span(oldPath), new U8Span(newPath));
|
||||
}
|
||||
|
||||
protected override Result GetEntryTypeImpl(out DirectoryEntryType entryType, string path)
|
||||
{
|
||||
var u8Path = new U8Span(path);
|
||||
|
||||
if (FsTable.GetFile(u8Path, out _).IsSuccess())
|
||||
{
|
||||
entryType = DirectoryEntryType.File;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (FsTable.GetDirectory(u8Path, out _).IsSuccess())
|
||||
{
|
||||
entryType = DirectoryEntryType.Directory;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
entryType = default;
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
protected override Result CommitImpl()
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result GetFileAttributesImpl(string path, out NxFileAttributes attributes)
|
||||
{
|
||||
var u8Path = new U8Span(path);
|
||||
|
||||
if (FsTable.GetFile(u8Path, out FileNode file).IsSuccess())
|
||||
{
|
||||
attributes = file.Attributes;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (FsTable.GetDirectory(u8Path, out DirectoryNode dir).IsSuccess())
|
||||
{
|
||||
attributes = dir.Attributes;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
attributes = default;
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
protected override Result SetFileAttributesImpl(string path, NxFileAttributes attributes)
|
||||
{
|
||||
var u8Path = new U8Span(path);
|
||||
|
||||
if (FsTable.GetFile(u8Path, out FileNode file).IsSuccess())
|
||||
{
|
||||
file.Attributes = attributes;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (FsTable.GetDirectory(u8Path, out DirectoryNode dir).IsSuccess())
|
||||
{
|
||||
dir.Attributes = attributes;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
protected override Result GetFileSizeImpl(out long fileSize, string path)
|
||||
{
|
||||
var u8Path = new U8Span(path);
|
||||
|
||||
if (FsTable.GetFile(u8Path, out FileNode file).IsSuccess())
|
||||
{
|
||||
return file.File.GetSize(out fileSize);
|
||||
}
|
||||
|
||||
fileSize = default;
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
// todo: Make a more generic MemoryFile-type class
|
||||
private class MemoryFile : FileBase
|
||||
{
|
||||
private OpenMode Mode { get; }
|
||||
private MemoryStreamAccessor BaseStream { get; }
|
||||
|
||||
public MemoryFile(OpenMode mode, MemoryStreamAccessor buffer)
|
||||
{
|
||||
BaseStream = buffer;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
protected override Result ReadImpl(out long bytesRead, long offset, Span<byte> destination, ReadOption options)
|
||||
{
|
||||
if (!Mode.HasFlag(OpenMode.Read))
|
||||
{
|
||||
bytesRead = 0;
|
||||
return ResultFs.InvalidOpenModeForRead.Log();
|
||||
}
|
||||
|
||||
return BaseStream.Read(out bytesRead, offset, destination);
|
||||
}
|
||||
|
||||
protected override Result WriteImpl(long offset, ReadOnlySpan<byte> source, WriteOption options)
|
||||
{
|
||||
if (!Mode.HasFlag(OpenMode.Write))
|
||||
{
|
||||
return ResultFs.InvalidOpenModeForWrite.Log();
|
||||
}
|
||||
|
||||
return BaseStream.Write(offset, source, Mode.HasFlag(OpenMode.AllowAppend));
|
||||
}
|
||||
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return BaseStream.Flush();
|
||||
}
|
||||
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
return BaseStream.SetSize(size);
|
||||
}
|
||||
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
return BaseStream.GetSize(out size);
|
||||
}
|
||||
}
|
||||
|
||||
private class MemoryDirectory : IDirectory
|
||||
{
|
||||
private OpenDirectoryMode Mode { get; }
|
||||
private DirectoryNode Directory { get; }
|
||||
private DirectoryNode CurrentDir { get; set; }
|
||||
private FileNode CurrentFile { get; set; }
|
||||
|
||||
public MemoryDirectory(DirectoryNode directory, OpenDirectoryMode mode)
|
||||
{
|
||||
Mode = mode;
|
||||
Directory = directory;
|
||||
CurrentDir = directory.ChildDirectory;
|
||||
CurrentFile = directory.ChildFile;
|
||||
}
|
||||
|
||||
public Result Read(out long entriesRead, Span<DirectoryEntry> entryBuffer)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Directory))
|
||||
{
|
||||
while (CurrentDir != null && i < entryBuffer.Length)
|
||||
{
|
||||
ref DirectoryEntry entry = ref entryBuffer[i];
|
||||
|
||||
StringUtils.Copy(entry.Name, CurrentDir.Name);
|
||||
entry.Name[PathTools.MaxPathLength] = 0;
|
||||
|
||||
entry.Type = DirectoryEntryType.Directory;
|
||||
entry.Attributes = CurrentDir.Attributes;
|
||||
entry.Size = 0;
|
||||
|
||||
i++;
|
||||
CurrentDir = CurrentDir.Next;
|
||||
}
|
||||
}
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.File))
|
||||
{
|
||||
while (CurrentFile != null && i < entryBuffer.Length)
|
||||
{
|
||||
ref DirectoryEntry entry = ref entryBuffer[i];
|
||||
|
||||
StringUtils.Copy(entry.Name, CurrentFile.Name);
|
||||
entry.Name[PathTools.MaxPathLength] = 0;
|
||||
|
||||
entry.Type = DirectoryEntryType.File;
|
||||
entry.Attributes = CurrentFile.Attributes;
|
||||
CurrentFile.File.GetSize(out entry.Size);
|
||||
|
||||
i++;
|
||||
CurrentFile = CurrentFile.Next;
|
||||
}
|
||||
}
|
||||
|
||||
entriesRead = i;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result GetEntryCount(out long entryCount)
|
||||
{
|
||||
long count = 0;
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Directory))
|
||||
{
|
||||
DirectoryNode current = Directory.ChildDirectory;
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
count++;
|
||||
current = current.Next;
|
||||
}
|
||||
}
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.File))
|
||||
{
|
||||
FileNode current = Directory.ChildFile;
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
count++;
|
||||
current = current.Next;
|
||||
}
|
||||
}
|
||||
|
||||
entryCount = count;
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
// todo: Replace with a class that uses multiple byte arrays as backing memory
|
||||
// so resizing doesn't involve copies
|
||||
/// <summary>
|
||||
/// Provides exclusive access to a <see cref="MemoryStream"/> object.
|
||||
/// Used by <see cref="MemoryFile"/> to enable opening a file multiple times with differing permissions.
|
||||
/// </summary>
|
||||
private class MemoryStreamAccessor
|
||||
{
|
||||
private const int MemStreamMaxLength = int.MaxValue;
|
||||
|
||||
private MemoryStream BaseStream { get; }
|
||||
private object Locker { get; } = new object();
|
||||
|
||||
public MemoryStreamAccessor(MemoryStream stream)
|
||||
{
|
||||
BaseStream = stream;
|
||||
}
|
||||
|
||||
public Result Read(out long bytesRead, long offset, Span<byte> destination)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (offset > BaseStream.Length)
|
||||
{
|
||||
bytesRead = default;
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
}
|
||||
|
||||
BaseStream.Position = offset;
|
||||
|
||||
bytesRead = BaseStream.Read(destination);
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public Result Write(long offset, ReadOnlySpan<byte> source, bool allowAppend)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (offset + source.Length > BaseStream.Length)
|
||||
{
|
||||
if (!allowAppend)
|
||||
{
|
||||
return ResultFs.FileExtensionWithoutOpenModeAllowAppend.Log();
|
||||
}
|
||||
|
||||
if (offset + source.Length > MemStreamMaxLength)
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
}
|
||||
|
||||
BaseStream.Position = offset;
|
||||
|
||||
BaseStream.Write(source);
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public Result Flush()
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result SetSize(long size)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (size > MemStreamMaxLength)
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
BaseStream.SetLength(size);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public Result GetSize(out long size)
|
||||
{
|
||||
size = BaseStream.Length;
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
private class FileNode
|
||||
{
|
||||
public MemoryStreamAccessor File { get; set; }
|
||||
public DirectoryNode Parent { get; set; }
|
||||
public U8String Name { get; set; }
|
||||
public NxFileAttributes Attributes { get; set; }
|
||||
public FileNode Next { get; set; }
|
||||
}
|
||||
|
||||
private class DirectoryNode
|
||||
{
|
||||
private NxFileAttributes _attributes = NxFileAttributes.Directory;
|
||||
|
||||
public NxFileAttributes Attributes
|
||||
{
|
||||
get => _attributes;
|
||||
set => _attributes = value | NxFileAttributes.Directory;
|
||||
}
|
||||
|
||||
public DirectoryNode Parent { get; set; }
|
||||
public U8String Name { get; set; }
|
||||
public DirectoryNode Next { get; set; }
|
||||
public DirectoryNode ChildDirectory { get; set; }
|
||||
public FileNode ChildFile { get; set; }
|
||||
}
|
||||
|
||||
private class FileTable
|
||||
{
|
||||
private DirectoryNode Root;
|
||||
|
||||
public FileTable()
|
||||
{
|
||||
Root = new DirectoryNode();
|
||||
Root.Name = new U8String("");
|
||||
}
|
||||
|
||||
public Result AddFile(U8Span path)
|
||||
{
|
||||
var parentPath = new U8Span(PathTools.GetParentDirectory(path));
|
||||
|
||||
Result rc = FindDirectory(parentPath, out DirectoryNode parent);
|
||||
if (rc.IsFailure()) return rc;
|
||||
var fileName = new U8Span(PathTools.GetLastSegment(path));
|
||||
|
||||
|
||||
return AddFile(fileName, parent);
|
||||
}
|
||||
|
||||
public Result AddDirectory(U8Span path)
|
||||
{
|
||||
var parentPath = new U8Span(PathTools.GetParentDirectory(path));
|
||||
|
||||
Result rc = FindDirectory(parentPath, out DirectoryNode parent);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var dirName = new U8Span(PathTools.GetLastSegment(path));
|
||||
|
||||
return AddDirectory(dirName, parent);
|
||||
}
|
||||
|
||||
public Result GetFile(U8Span path, out FileNode file)
|
||||
{
|
||||
return FindFile(path, out file);
|
||||
}
|
||||
|
||||
public Result GetDirectory(U8Span path, out DirectoryNode dir)
|
||||
{
|
||||
return FindDirectory(path, out dir);
|
||||
}
|
||||
|
||||
public Result RenameDirectory(U8Span oldPath, U8Span newPath)
|
||||
{
|
||||
Result rc = FindDirectory(oldPath, out DirectoryNode directory);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var newParentPath = new U8Span(PathTools.GetParentDirectory(newPath));
|
||||
|
||||
rc = FindDirectory(newParentPath, out DirectoryNode newParent);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var newName = new U8Span(PathTools.GetLastSegment(newPath));
|
||||
|
||||
if (TryFindChildDirectory(newName, newParent, out _) || TryFindChildFile(newName, newParent, out _))
|
||||
{
|
||||
return ResultFs.PathAlreadyExists.Log();
|
||||
}
|
||||
|
||||
if (directory.Parent != newParent)
|
||||
{
|
||||
if (!UnlinkDirectory(directory))
|
||||
{
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
}
|
||||
|
||||
LinkDirectory(directory, newParent);
|
||||
}
|
||||
|
||||
if (StringUtils.Compare(directory.Name, newName) != 0)
|
||||
{
|
||||
directory.Name = newName.ToU8String();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result RenameFile(U8Span oldPath, U8Span newPath)
|
||||
{
|
||||
Result rc = FindFile(oldPath, out FileNode file);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var newParentPath = new U8Span(PathTools.GetParentDirectory(newPath));
|
||||
|
||||
rc = FindDirectory(newParentPath, out DirectoryNode newParent);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var newName = new U8Span(PathTools.GetLastSegment(newPath));
|
||||
|
||||
if (TryFindChildDirectory(newName, newParent, out _) || TryFindChildFile(newName, newParent, out _))
|
||||
{
|
||||
return ResultFs.PathAlreadyExists.Log();
|
||||
}
|
||||
|
||||
if (file.Parent != newParent)
|
||||
{
|
||||
if (!UnlinkFile(file))
|
||||
{
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
}
|
||||
|
||||
LinkFile(file, newParent);
|
||||
}
|
||||
|
||||
|
||||
if (StringUtils.Compare(file.Name, newName) != 0)
|
||||
{
|
||||
file.Name = newName.ToU8String();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result DeleteDirectory(U8Span path, bool recursive)
|
||||
{
|
||||
Result rc = FindDirectory(path, out DirectoryNode directory);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (!recursive && (directory.ChildDirectory != null || directory.ChildFile != null))
|
||||
{
|
||||
return ResultFs.DirectoryNotEmpty.Log();
|
||||
}
|
||||
|
||||
UnlinkDirectory(directory);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result DeleteFile(U8Span path)
|
||||
{
|
||||
Result rc = FindFile(path, out FileNode file);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
UnlinkFile(file);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result CleanDirectory(U8Span path)
|
||||
{
|
||||
Result rc = FindDirectory(path, out DirectoryNode directory);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
directory.ChildDirectory = null;
|
||||
directory.ChildFile = null;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result AddFile(U8Span name, DirectoryNode parent)
|
||||
{
|
||||
if (TryFindChildDirectory(name, parent, out _))
|
||||
{
|
||||
return ResultFs.PathAlreadyExists.Log();
|
||||
}
|
||||
|
||||
if (TryFindChildFile(name, parent, out _))
|
||||
{
|
||||
return ResultFs.PathAlreadyExists.Log();
|
||||
}
|
||||
|
||||
var newFileNode = new FileNode
|
||||
{
|
||||
Name = name.ToU8String(),
|
||||
File = new MemoryStreamAccessor(new MemoryStream())
|
||||
};
|
||||
|
||||
LinkFile(newFileNode, parent);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result AddDirectory(U8Span name, DirectoryNode parent)
|
||||
{
|
||||
if (TryFindChildDirectory(name, parent, out _))
|
||||
{
|
||||
return ResultFs.PathAlreadyExists.Log();
|
||||
}
|
||||
|
||||
if (TryFindChildFile(name, parent, out _))
|
||||
{
|
||||
return ResultFs.PathAlreadyExists.Log();
|
||||
}
|
||||
|
||||
var newDirNode = new DirectoryNode { Name = name.ToU8String() };
|
||||
|
||||
LinkDirectory(newDirNode, parent);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result FindFile(U8Span path, out FileNode file)
|
||||
{
|
||||
var parentPath = new U8Span(PathTools.GetParentDirectory(path));
|
||||
|
||||
Result rc = FindDirectory(parentPath, out DirectoryNode parentNode);
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
file = default;
|
||||
return rc;
|
||||
}
|
||||
|
||||
var fileName = new U8Span(PathTools.GetLastSegment(path));
|
||||
|
||||
if (TryFindChildFile(fileName, parentNode, out file))
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
private Result FindDirectory(U8Span path, out DirectoryNode directory)
|
||||
{
|
||||
var parser = new PathParser(path);
|
||||
DirectoryNode current = Root;
|
||||
|
||||
while (parser.MoveNext())
|
||||
{
|
||||
if (!TryFindChildDirectory(new U8Span(parser.GetCurrent()), current, out DirectoryNode child))
|
||||
{
|
||||
directory = default;
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
current = child;
|
||||
}
|
||||
|
||||
directory = current;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private bool TryFindChildDirectory(U8Span name, DirectoryNode parent, out DirectoryNode child)
|
||||
{
|
||||
DirectoryNode currentChild = parent.ChildDirectory;
|
||||
|
||||
while (currentChild != null)
|
||||
{
|
||||
if (StringUtils.Compare(name, currentChild.Name) == 0)
|
||||
{
|
||||
child = currentChild;
|
||||
return true;
|
||||
}
|
||||
|
||||
currentChild = currentChild.Next;
|
||||
}
|
||||
|
||||
child = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryFindChildFile(U8Span name, DirectoryNode parent, out FileNode child)
|
||||
{
|
||||
FileNode currentChild = parent.ChildFile;
|
||||
|
||||
while (currentChild != null)
|
||||
{
|
||||
if (StringUtils.Compare(name, currentChild.Name) == 0)
|
||||
{
|
||||
child = currentChild;
|
||||
return true;
|
||||
}
|
||||
|
||||
currentChild = currentChild.Next;
|
||||
}
|
||||
|
||||
child = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void LinkDirectory(DirectoryNode dir, DirectoryNode parentDir)
|
||||
{
|
||||
Debug.Assert(dir.Parent == null);
|
||||
Debug.Assert(dir.Next == null);
|
||||
|
||||
dir.Next = parentDir.ChildDirectory;
|
||||
dir.Parent = parentDir;
|
||||
parentDir.ChildDirectory = dir;
|
||||
}
|
||||
|
||||
private bool UnlinkDirectory(DirectoryNode dir)
|
||||
{
|
||||
Debug.Assert(dir.Parent != null);
|
||||
|
||||
DirectoryNode parent = dir.Parent;
|
||||
|
||||
if (parent.ChildDirectory == null)
|
||||
return false;
|
||||
|
||||
if (parent.ChildDirectory == dir)
|
||||
{
|
||||
parent.ChildDirectory = dir.Next;
|
||||
dir.Parent = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DirectoryNode current = parent.ChildDirectory;
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
if (current.Next == dir)
|
||||
{
|
||||
current.Next = dir.Next;
|
||||
dir.Parent = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
current = current.Next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void LinkFile(FileNode file, DirectoryNode parentDir)
|
||||
{
|
||||
Debug.Assert(file.Parent == null);
|
||||
Debug.Assert(file.Next == null);
|
||||
|
||||
file.Next = parentDir.ChildFile;
|
||||
file.Parent = parentDir;
|
||||
parentDir.ChildFile = file;
|
||||
}
|
||||
|
||||
private bool UnlinkFile(FileNode file)
|
||||
{
|
||||
Debug.Assert(file.Parent != null);
|
||||
|
||||
DirectoryNode parent = file.Parent;
|
||||
|
||||
if (parent.ChildFile == null)
|
||||
return false;
|
||||
|
||||
if (parent.ChildFile == file)
|
||||
{
|
||||
parent.ChildFile = file.Next;
|
||||
file.Parent = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FileNode current = parent.ChildFile;
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
if (current.Next == file)
|
||||
{
|
||||
current.Next = file.Next;
|
||||
file.Parent = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
current = current.Next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -262,6 +262,22 @@ namespace LibHac.FsSystem
|
||||
return path.Slice(i, path.Length - i);
|
||||
}
|
||||
|
||||
public static ReadOnlySpan<byte> GetLastSegment(ReadOnlySpan<byte> path)
|
||||
{
|
||||
Debug.Assert(IsNormalized(path));
|
||||
|
||||
if (path.Length == 0)
|
||||
return path;
|
||||
|
||||
int endIndex = path[path.Length - 1] == DirectorySeparator ? path.Length - 1 : path.Length;
|
||||
int i = endIndex;
|
||||
|
||||
while (i >= 1 && path[i - 1] != '/') i--;
|
||||
|
||||
i = Math.Max(i, 0);
|
||||
return path.Slice(i, endIndex - i);
|
||||
}
|
||||
|
||||
public static bool IsNormalized(ReadOnlySpan<char> path)
|
||||
{
|
||||
var state = NormalizeState.Initial;
|
||||
|
654
tests/LibHac.Tests/InMemoryFileSystemTests.cs
Normal file
654
tests/LibHac.Tests/InMemoryFileSystemTests.cs
Normal file
@ -0,0 +1,654 @@
|
||||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using Xunit;
|
||||
|
||||
namespace LibHac.Tests
|
||||
{
|
||||
public class InMemoryFileSystemTests
|
||||
{
|
||||
private IAttributeFileSystem GetFileSystem()
|
||||
{
|
||||
return new InMemoryFileSystem();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateFileWithNoParentDirectory()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
Result rc = fs.CreateFile("/dir/file", 0, CreateFileOptions.None);
|
||||
|
||||
Assert.Equal(ResultFs.PathNotFound, rc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RootDirectoryHasCorrectEntryType()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
Result rc = fs.GetEntryType(out DirectoryEntryType type, "/");
|
||||
|
||||
Assert.True(rc.IsSuccess());
|
||||
Assert.Equal(DirectoryEntryType.Directory, type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreatedFileHasCorrectSize()
|
||||
{
|
||||
const long expectedSize = 12345;
|
||||
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateFile("/file", expectedSize, CreateFileOptions.None);
|
||||
|
||||
fs.OpenFile(out IFile file, "/file", OpenMode.Read);
|
||||
Result rc = file.GetSize(out long fileSize);
|
||||
|
||||
Assert.True(rc.IsSuccess());
|
||||
Assert.Equal(expectedSize, fileSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadDataWrittenToFileAfterReopening()
|
||||
{
|
||||
var data = new byte[] { 7, 4, 1, 0, 8, 5, 2, 9, 6, 3 };
|
||||
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateFile("/file", data.Length, CreateFileOptions.None);
|
||||
|
||||
fs.OpenFile(out IFile file, "/file", OpenMode.Write);
|
||||
file.Write(0, data, WriteOption.None);
|
||||
file.Dispose();
|
||||
|
||||
var readData = new byte[data.Length];
|
||||
|
||||
fs.OpenFile(out file, "/file", OpenMode.Read);
|
||||
Result rc = file.Read(out long bytesRead, 0, readData, ReadOption.None);
|
||||
file.Dispose();
|
||||
|
||||
Assert.True(rc.IsSuccess());
|
||||
Assert.Equal(data.Length, bytesRead);
|
||||
Assert.Equal(data, readData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadDataWrittenToFileAfterRenaming()
|
||||
{
|
||||
var data = new byte[] { 7, 4, 1, 0, 8, 5, 2, 9, 6, 3 };
|
||||
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateFile("/file", data.Length, CreateFileOptions.None);
|
||||
|
||||
fs.OpenFile(out IFile file, "/file", OpenMode.Write);
|
||||
file.Write(0, data, WriteOption.None);
|
||||
file.Dispose();
|
||||
|
||||
fs.RenameFile("/file", "/renamed");
|
||||
|
||||
var readData = new byte[data.Length];
|
||||
|
||||
fs.OpenFile(out file, "/renamed", OpenMode.Read);
|
||||
Result rc = file.Read(out long bytesRead, 0, readData, ReadOption.None);
|
||||
file.Dispose();
|
||||
|
||||
Assert.True(rc.IsSuccess());
|
||||
Assert.Equal(data.Length, bytesRead);
|
||||
Assert.Equal(data, readData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenFileAsDirectory()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateFile("/file", 0, CreateFileOptions.None);
|
||||
|
||||
Result rc = fs.OpenDirectory(out _, "/file", OpenDirectoryMode.All);
|
||||
|
||||
Assert.Equal(ResultFs.PathNotFound, rc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenDirectoryAsFile()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir");
|
||||
|
||||
Result rc = fs.OpenFile(out _, "/dir", OpenMode.All);
|
||||
|
||||
Assert.Equal(ResultFs.PathNotFound, rc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteNonexistentFile()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
Result rc = fs.DeleteFile("/file");
|
||||
Assert.Equal(ResultFs.PathNotFound, rc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteNonexistentDirectory()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
Result rc = fs.DeleteDirectory("/dir");
|
||||
Assert.Equal(ResultFs.PathNotFound, rc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteFile()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateFile("/file", 0, CreateFileOptions.None);
|
||||
|
||||
Result rcDelete = fs.DeleteFile("/file");
|
||||
Result rcEntry = fs.GetEntryType(out _, "/file");
|
||||
|
||||
Assert.True(rcDelete.IsSuccess());
|
||||
Assert.Equal(ResultFs.PathNotFound, rcEntry);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteFileWithSiblingA()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateFile("/file1", 0, CreateFileOptions.None);
|
||||
fs.CreateFile("/file2", 0, CreateFileOptions.None);
|
||||
|
||||
Result rcDelete = fs.DeleteFile("/file2");
|
||||
Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/file1");
|
||||
Result rcEntry2 = fs.GetEntryType(out _, "/file2");
|
||||
|
||||
Assert.True(rcDelete.IsSuccess());
|
||||
Assert.True(rcEntry1.IsSuccess());
|
||||
Assert.Equal(ResultFs.PathNotFound, rcEntry2);
|
||||
|
||||
Assert.Equal(DirectoryEntryType.File, dir1Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteFileWithSiblingB()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateFile("/file2", 0, CreateFileOptions.None);
|
||||
fs.CreateFile("/file1", 0, CreateFileOptions.None);
|
||||
|
||||
Result rcDelete = fs.DeleteFile("/file2");
|
||||
Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/file1");
|
||||
Result rcEntry2 = fs.GetEntryType(out _, "/file2");
|
||||
|
||||
Assert.True(rcDelete.IsSuccess());
|
||||
Assert.True(rcEntry1.IsSuccess());
|
||||
Assert.Equal(ResultFs.PathNotFound, rcEntry2);
|
||||
|
||||
Assert.Equal(DirectoryEntryType.File, dir1Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteDirectory()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir");
|
||||
|
||||
Result rcDelete = fs.DeleteDirectory("/dir");
|
||||
Result rcEntry = fs.GetEntryType(out _, "/dir");
|
||||
|
||||
Assert.True(rcDelete.IsSuccess());
|
||||
Assert.Equal(ResultFs.PathNotFound, rcEntry);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteDirectoryWithSiblingA()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir1");
|
||||
fs.CreateDirectory("/dir2");
|
||||
|
||||
Result rcDelete = fs.DeleteDirectory("/dir2");
|
||||
Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1");
|
||||
Result rcEntry2 = fs.GetEntryType(out _, "/dir2");
|
||||
|
||||
Assert.True(rcDelete.IsSuccess());
|
||||
Assert.True(rcEntry1.IsSuccess());
|
||||
Assert.Equal(ResultFs.PathNotFound, rcEntry2);
|
||||
|
||||
Assert.Equal(DirectoryEntryType.Directory, dir1Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteDirectoryWithSiblingB()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir2");
|
||||
fs.CreateDirectory("/dir1");
|
||||
|
||||
Result rcDelete = fs.DeleteDirectory("/dir2");
|
||||
Result rcEntry1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1");
|
||||
Result rcEntry2 = fs.GetEntryType(out _, "/dir2");
|
||||
|
||||
Assert.True(rcDelete.IsSuccess());
|
||||
Assert.True(rcEntry1.IsSuccess());
|
||||
Assert.Equal(ResultFs.PathNotFound, rcEntry2);
|
||||
|
||||
Assert.Equal(DirectoryEntryType.Directory, dir1Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteDirectoryWithChildren()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir");
|
||||
fs.CreateFile("/dir/file", 0, CreateFileOptions.None);
|
||||
|
||||
Result rc = fs.DeleteDirectory("/dir");
|
||||
|
||||
Assert.Equal(ResultFs.DirectoryNotEmpty, rc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteDirectoryRecursively()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir");
|
||||
fs.CreateDirectory("/dir/dir2");
|
||||
fs.CreateFile("/dir/file1", 0, CreateFileOptions.None);
|
||||
|
||||
Result rcDelete = fs.DeleteDirectoryRecursively("/dir");
|
||||
|
||||
Result rcDir1Type = fs.GetEntryType(out _, "/dir");
|
||||
Result rcDir2Type = fs.GetEntryType(out _, "/dir/dir2");
|
||||
Result rcFileType = fs.GetEntryType(out _, "/dir/file1");
|
||||
|
||||
Assert.True(rcDelete.IsSuccess());
|
||||
|
||||
Assert.Equal(ResultFs.PathNotFound, rcDir1Type);
|
||||
Assert.Equal(ResultFs.PathNotFound, rcDir2Type);
|
||||
Assert.Equal(ResultFs.PathNotFound, rcFileType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CleanDirectoryRecursively()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir");
|
||||
fs.CreateDirectory("/dir/dir2");
|
||||
fs.CreateFile("/dir/file1", 0, CreateFileOptions.None);
|
||||
|
||||
Result rcDelete = fs.CleanDirectoryRecursively("/dir");
|
||||
|
||||
Result rcDir1Type = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir");
|
||||
Result rcDir2Type = fs.GetEntryType(out _, "/dir/dir2");
|
||||
Result rcFileType = fs.GetEntryType(out _, "/dir/file1");
|
||||
|
||||
Assert.True(rcDelete.IsSuccess());
|
||||
|
||||
Assert.True(rcDir1Type.IsSuccess());
|
||||
Assert.Equal(DirectoryEntryType.Directory, dir1Type);
|
||||
|
||||
Assert.Equal(ResultFs.PathNotFound, rcDir2Type);
|
||||
Assert.Equal(ResultFs.PathNotFound, rcFileType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateFile()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateFile("/file", 0, CreateFileOptions.None);
|
||||
Result rc = fs.GetEntryType(out DirectoryEntryType type, "/file");
|
||||
|
||||
Assert.True(rc.IsSuccess());
|
||||
Assert.Equal(DirectoryEntryType.File, type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateFileWithTrailingSlash()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateFile("/file/", 0, CreateFileOptions.None);
|
||||
Result rc = fs.GetEntryType(out DirectoryEntryType type, "/file/");
|
||||
|
||||
Assert.True(rc.IsSuccess());
|
||||
Assert.Equal(DirectoryEntryType.File, type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDirectory()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir");
|
||||
Result rc = fs.GetEntryType(out DirectoryEntryType type, "/dir");
|
||||
|
||||
Assert.True(rc.IsSuccess());
|
||||
Assert.Equal(DirectoryEntryType.Directory, type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDirectoryWithTrailingSlash()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir/");
|
||||
Result rc = fs.GetEntryType(out DirectoryEntryType type, "/dir/");
|
||||
|
||||
Assert.True(rc.IsSuccess());
|
||||
Assert.Equal(DirectoryEntryType.Directory, type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateMultipleDirectories()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir1");
|
||||
fs.CreateDirectory("/dir2");
|
||||
Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/dir1");
|
||||
Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/dir2");
|
||||
|
||||
Assert.True(rc1.IsSuccess());
|
||||
Assert.True(rc2.IsSuccess());
|
||||
Assert.Equal(DirectoryEntryType.Directory, type1);
|
||||
Assert.Equal(DirectoryEntryType.Directory, type2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateMultipleNestedDirectories()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir1");
|
||||
fs.CreateDirectory("/dir2");
|
||||
|
||||
fs.CreateDirectory("/dir1/dir1a");
|
||||
fs.CreateDirectory("/dir2/dir2a");
|
||||
Result rc1 = fs.GetEntryType(out DirectoryEntryType type1, "/dir1/dir1a");
|
||||
Result rc2 = fs.GetEntryType(out DirectoryEntryType type2, "/dir2/dir2a");
|
||||
|
||||
Assert.True(rc1.IsSuccess());
|
||||
Assert.True(rc2.IsSuccess());
|
||||
Assert.Equal(DirectoryEntryType.Directory, type1);
|
||||
Assert.Equal(DirectoryEntryType.Directory, type2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDirectoryWithAttribute()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir1", NxFileAttributes.None);
|
||||
fs.CreateDirectory("/dir2", NxFileAttributes.Archive);
|
||||
|
||||
Result rc1 = fs.GetFileAttributes("/dir1", out NxFileAttributes type1);
|
||||
Result rc2 = fs.GetFileAttributes("/dir2", out NxFileAttributes type2);
|
||||
|
||||
Assert.True(rc1.IsSuccess());
|
||||
Assert.True(rc2.IsSuccess());
|
||||
|
||||
Assert.Equal(NxFileAttributes.Directory, type1);
|
||||
Assert.Equal(NxFileAttributes.Directory | NxFileAttributes.Archive, type2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenameFile()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateFile("/file1", 12345, CreateFileOptions.None);
|
||||
|
||||
Result rcRename = fs.RenameFile("/file1", "/file2");
|
||||
|
||||
Result rcOpen = fs.OpenFile(out IFile file, "/file2", OpenMode.All);
|
||||
Result rcSize = file.GetSize(out long fileSize);
|
||||
|
||||
Result rcOldType = fs.GetEntryType(out _, "/file1");
|
||||
|
||||
Assert.True(rcRename.IsSuccess());
|
||||
Assert.True(rcOpen.IsSuccess());
|
||||
Assert.True(rcSize.IsSuccess());
|
||||
|
||||
Assert.Equal(12345, fileSize);
|
||||
Assert.Equal(ResultFs.PathNotFound, rcOldType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenameFileWhenDestExists()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateFile("/file1", 12345, CreateFileOptions.None);
|
||||
fs.CreateFile("/file2", 54321, CreateFileOptions.None);
|
||||
|
||||
Result rcRename = fs.RenameFile("/file1", "/file2");
|
||||
|
||||
Result rcFile1 = fs.GetEntryType(out DirectoryEntryType file1Type, "/file1");
|
||||
Result rcFile2 = fs.GetEntryType(out DirectoryEntryType file2Type, "/file2");
|
||||
|
||||
Assert.Equal(ResultFs.PathAlreadyExists, rcRename);
|
||||
|
||||
Assert.True(rcFile1.IsSuccess());
|
||||
Assert.True(rcFile2.IsSuccess());
|
||||
Assert.Equal(DirectoryEntryType.File, file1Type);
|
||||
Assert.Equal(DirectoryEntryType.File, file2Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenameDirectory()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir1");
|
||||
Result rcRename = fs.RenameDirectory("/dir1", "/dir2");
|
||||
|
||||
Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2");
|
||||
Result rcDir1 = fs.GetEntryType(out _, "/dir1");
|
||||
|
||||
Assert.True(rcRename.IsSuccess());
|
||||
|
||||
Assert.True(rcDir2.IsSuccess());
|
||||
Assert.Equal(DirectoryEntryType.Directory, dir2Type);
|
||||
|
||||
Assert.Equal(ResultFs.PathNotFound, rcDir1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenameDirectoryWithChildren()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir1");
|
||||
fs.CreateDirectory("/dir1/dirC");
|
||||
fs.CreateFile("/dir1/file1", 0, CreateFileOptions.None);
|
||||
|
||||
Result rcRename = fs.RenameDirectory("/dir1", "/dir2");
|
||||
|
||||
// Check that renamed structure exists
|
||||
Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2");
|
||||
Result rcDirC = fs.GetEntryType(out DirectoryEntryType dir1CType, "/dir2/dirC");
|
||||
Result rcFile1 = fs.GetEntryType(out DirectoryEntryType file1Type, "/dir2/file1");
|
||||
|
||||
// Check that old structure doesn't exist
|
||||
Result rcDir1 = fs.GetEntryType(out _, "/dir1");
|
||||
Result rcDirCOld = fs.GetEntryType(out _, "/dir1/dirC");
|
||||
Result rcFile1Old = fs.GetEntryType(out _, "/dir1/file1");
|
||||
|
||||
Assert.True(rcRename.IsSuccess());
|
||||
|
||||
Assert.True(rcDir2.IsSuccess());
|
||||
Assert.True(rcDirC.IsSuccess());
|
||||
Assert.True(rcFile1.IsSuccess());
|
||||
|
||||
Assert.Equal(DirectoryEntryType.Directory, dir2Type);
|
||||
Assert.Equal(DirectoryEntryType.Directory, dir1CType);
|
||||
Assert.Equal(DirectoryEntryType.File, file1Type);
|
||||
|
||||
Assert.Equal(ResultFs.PathNotFound, rcDir1);
|
||||
Assert.Equal(ResultFs.PathNotFound, rcDirCOld);
|
||||
Assert.Equal(ResultFs.PathNotFound, rcFile1Old);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenameDirectoryToDifferentParent()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/parent1");
|
||||
fs.CreateDirectory("/parent2");
|
||||
fs.CreateDirectory("/parent1/dir1");
|
||||
|
||||
Result rcRename = fs.RenameDirectory("/parent1/dir1", "/parent2/dir2");
|
||||
|
||||
Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/parent2/dir2");
|
||||
Result rcDir1 = fs.GetEntryType(out _, "/parent1/dir1");
|
||||
|
||||
Assert.True(rcRename.IsSuccess());
|
||||
|
||||
Assert.Equal(Result.Success, rcDir2);
|
||||
Assert.True(rcDir2.IsSuccess());
|
||||
Assert.Equal(DirectoryEntryType.Directory, dir2Type);
|
||||
|
||||
Assert.Equal(ResultFs.PathNotFound, rcDir1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenameDirectoryWhenDestExists()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
|
||||
fs.CreateDirectory("/dir1");
|
||||
fs.CreateDirectory("/dir2");
|
||||
|
||||
Result rcRename = fs.RenameDirectory("/dir1", "/dir2");
|
||||
|
||||
Result rcDir1 = fs.GetEntryType(out DirectoryEntryType dir1Type, "/dir1");
|
||||
Result rcDir2 = fs.GetEntryType(out DirectoryEntryType dir2Type, "/dir2");
|
||||
|
||||
Assert.Equal(ResultFs.PathAlreadyExists, rcRename);
|
||||
|
||||
Assert.True(rcDir1.IsSuccess());
|
||||
Assert.True(rcDir2.IsSuccess());
|
||||
Assert.Equal(DirectoryEntryType.Directory, dir1Type);
|
||||
Assert.Equal(DirectoryEntryType.Directory, dir2Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetFileSize()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
fs.CreateFile("/file", 0, CreateFileOptions.None);
|
||||
|
||||
fs.OpenFile(out IFile file, "/file", OpenMode.All);
|
||||
Result rc = file.SetSize(54321);
|
||||
file.Dispose();
|
||||
|
||||
fs.OpenFile(out file, "/file", OpenMode.All);
|
||||
file.GetSize(out long fileSize);
|
||||
file.Dispose();
|
||||
|
||||
Assert.True(rc.IsSuccess());
|
||||
Assert.Equal(54321, fileSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetFileAttributes()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
fs.CreateFile("/file", 0, CreateFileOptions.None);
|
||||
|
||||
Result rcSet = fs.SetFileAttributes("/file", NxFileAttributes.Archive);
|
||||
Result rcGet = fs.GetFileAttributes("/file", out NxFileAttributes attributes);
|
||||
|
||||
Assert.True(rcSet.IsSuccess());
|
||||
Assert.True(rcGet.IsSuccess());
|
||||
Assert.Equal(NxFileAttributes.Archive, attributes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IterateDirectory()
|
||||
{
|
||||
IAttributeFileSystem fs = GetFileSystem();
|
||||
fs.CreateDirectory("/dir");
|
||||
fs.CreateDirectory("/dir/dir1");
|
||||
fs.CreateFile("/dir/dir1/file1", 0, CreateFileOptions.None);
|
||||
fs.CreateFile("/dir/file1", 0, CreateFileOptions.None);
|
||||
fs.CreateFile("/dir/file2", 0, CreateFileOptions.None);
|
||||
|
||||
Result rc = fs.OpenDirectory(out IDirectory dir, "/dir", OpenDirectoryMode.All);
|
||||
Assert.True(rc.IsSuccess());
|
||||
|
||||
var entry1 = new DirectoryEntry();
|
||||
var entry2 = new DirectoryEntry();
|
||||
var entry3 = new DirectoryEntry();
|
||||
var entry4 = new DirectoryEntry();
|
||||
|
||||
Assert.True(dir.Read(out long entriesRead1, SpanHelpers.AsSpan(ref entry1)).IsSuccess());
|
||||
Assert.True(dir.Read(out long entriesRead2, SpanHelpers.AsSpan(ref entry2)).IsSuccess());
|
||||
Assert.True(dir.Read(out long entriesRead3, SpanHelpers.AsSpan(ref entry3)).IsSuccess());
|
||||
Assert.True(dir.Read(out long entriesRead4, SpanHelpers.AsSpan(ref entry4)).IsSuccess());
|
||||
|
||||
Assert.Equal(1, entriesRead1);
|
||||
Assert.Equal(1, entriesRead2);
|
||||
Assert.Equal(1, entriesRead3);
|
||||
Assert.Equal(0, entriesRead4);
|
||||
|
||||
bool dir1Read = false;
|
||||
bool file1Read = false;
|
||||
bool file2Read = false;
|
||||
|
||||
// Entries are not guaranteed to be in any particular order
|
||||
CheckEntry(ref entry1);
|
||||
CheckEntry(ref entry2);
|
||||
CheckEntry(ref entry3);
|
||||
|
||||
Assert.True(dir1Read);
|
||||
Assert.True(file1Read);
|
||||
Assert.True(file2Read);
|
||||
|
||||
void CheckEntry(ref DirectoryEntry entry)
|
||||
{
|
||||
switch (StringUtils.Utf8ZToString(entry.Name))
|
||||
{
|
||||
case "dir1":
|
||||
Assert.False(dir1Read);
|
||||
Assert.Equal(DirectoryEntryType.Directory, entry.Type);
|
||||
|
||||
dir1Read = true;
|
||||
break;
|
||||
|
||||
case "file1":
|
||||
Assert.False(file1Read);
|
||||
Assert.Equal(DirectoryEntryType.File, entry.Type);
|
||||
|
||||
file1Read = true;
|
||||
break;
|
||||
|
||||
case "file2":
|
||||
Assert.False(file2Read);
|
||||
Assert.Equal(DirectoryEntryType.File, entry.Type);
|
||||
|
||||
file2Read = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -299,5 +299,29 @@ namespace LibHac.Tests
|
||||
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
public static object[][] GetLastSegmentTestItems =
|
||||
{
|
||||
new object[] {"/a/bb/ccc", "ccc"},
|
||||
new object[] {"/a/bb/ccc/", "ccc"},
|
||||
new object[] {"/a/bb", "bb"},
|
||||
new object[] {"/a/bb/", "bb"},
|
||||
new object[] {"/a", "a"},
|
||||
new object[] {"/a/", "a"},
|
||||
new object[] {"/", ""},
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetLastSegmentTestItems))]
|
||||
public static void GetLastSegmentTest(string path, string expected)
|
||||
{
|
||||
var u8Path = path.ToU8String();
|
||||
|
||||
ReadOnlySpan<byte> fileName = PathTools.GetLastSegment(u8Path);
|
||||
|
||||
string actual = StringUtils.Utf8ZToString(fileName);
|
||||
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user