mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Add rename functions to SaveDataFileSystemCore
This commit is contained in:
parent
25a66cf4df
commit
e3e6411aa6
@ -4,5 +4,8 @@
|
||||
{
|
||||
public static string DestSpanTooSmall => "Destination array is not long enough to hold the requested data.";
|
||||
public static string NcaSectionMissing => "NCA section does not exist.";
|
||||
public static string DestPathIsSubPath => "The destination directory is a subdirectory of the source directory.";
|
||||
public static string DestPathAlreadyExists => "Destination path already exists.";
|
||||
public static string PartialPathNotFound => "Could not find a part of the path.";
|
||||
}
|
||||
}
|
||||
|
@ -171,6 +171,52 @@ namespace LibHac.IO
|
||||
return state == NormalizeState.Normal || state == NormalizeState.Delimiter;
|
||||
}
|
||||
|
||||
public static bool IsSubPath(ReadOnlySpan<char> rootPath, ReadOnlySpan<char> path)
|
||||
{
|
||||
Debug.Assert(IsNormalized(rootPath));
|
||||
Debug.Assert(IsNormalized(path));
|
||||
|
||||
if (path.Length <= rootPath.Length) return false;
|
||||
|
||||
for (int i = 0; i < rootPath.Length; i++)
|
||||
{
|
||||
if (rootPath[i] != path[i]) return false;
|
||||
}
|
||||
|
||||
// The input root path might or might not have a trailing slash.
|
||||
// Both are treated the same.
|
||||
int rootLength = rootPath[rootPath.Length - 1] == DirectorySeparator
|
||||
? rootPath.Length - 1
|
||||
: rootPath.Length;
|
||||
|
||||
// Return true if the character after the root path is a separator,
|
||||
// and if the possible sub path continues past that point.
|
||||
return path[rootLength] == DirectorySeparator && path.Length > rootLength + 1;
|
||||
}
|
||||
|
||||
public static bool IsSubPath(ReadOnlySpan<byte> rootPath, ReadOnlySpan<byte> path)
|
||||
{
|
||||
Debug.Assert(IsNormalized(rootPath));
|
||||
Debug.Assert(IsNormalized(path));
|
||||
|
||||
if (path.Length <= rootPath.Length) return false;
|
||||
|
||||
for (int i = 0; i < rootPath.Length; i++)
|
||||
{
|
||||
if (rootPath[i] != path[i]) return false;
|
||||
}
|
||||
|
||||
// The input root path might or might not have a trailing slash.
|
||||
// Both are treated the same.
|
||||
int rootLength = rootPath[rootPath.Length - 1] == DirectorySeparator
|
||||
? rootPath.Length - 1
|
||||
: rootPath.Length;
|
||||
|
||||
// Return true if the character after the root path is a separator,
|
||||
// and if the possible sub path continues past that point.
|
||||
return path[rootLength] == DirectorySeparator && path.Length > rootLength + 1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool IsDirectorySeparator(char c)
|
||||
{
|
||||
|
@ -105,8 +105,7 @@ namespace LibHac.IO.Save
|
||||
|
||||
if (path == "/") throw new ArgumentException("Path cannot be empty");
|
||||
|
||||
SaveFindPosition emptyDir = default;
|
||||
CreateDirectoryRecursive(pathBytes, ref emptyDir);
|
||||
CreateDirectoryRecursive(pathBytes);
|
||||
}
|
||||
|
||||
private void CreateFileRecursive(ReadOnlySpan<byte> path, ref SaveFileInfo fileInfo)
|
||||
@ -117,25 +116,24 @@ namespace LibHac.IO.Save
|
||||
int parentIndex = CreateParentDirectoryRecursive(ref parser, ref key);
|
||||
|
||||
int index = FileTable.GetIndexFromKey(ref key).Index;
|
||||
var fileEntry = new TableEntry<SaveFileInfo>();
|
||||
TableEntry<SaveFileInfo> fileEntry = default;
|
||||
|
||||
if (index < 0)
|
||||
// File already exists. Update file info.
|
||||
if (index >= 0)
|
||||
{
|
||||
index = FileTable.Add(ref key, ref fileEntry);
|
||||
|
||||
DirectoryTable.GetValue(parentIndex, out TableEntry<SaveFindPosition> parentEntry);
|
||||
|
||||
fileEntry.NextSibling = parentEntry.Value.NextFile;
|
||||
parentEntry.Value.NextFile = index;
|
||||
|
||||
DirectoryTable.SetValue(parentIndex, ref parentEntry);
|
||||
FileTable.GetValue(index, out fileEntry);
|
||||
fileEntry.Value = fileInfo;
|
||||
FileTable.SetValue(index, ref fileEntry);
|
||||
return;
|
||||
}
|
||||
|
||||
fileEntry.Value = fileInfo;
|
||||
FileTable.SetValue(index, ref fileEntry);
|
||||
index = FileTable.Add(ref key, ref fileEntry);
|
||||
|
||||
LinkFileToParent(parentIndex, index);
|
||||
}
|
||||
|
||||
private void CreateDirectoryRecursive(ReadOnlySpan<byte> path, ref SaveFindPosition dirInfo)
|
||||
private void CreateDirectoryRecursive(ReadOnlySpan<byte> path)
|
||||
{
|
||||
var parser = new PathParser(path);
|
||||
var key = new SaveEntryKey(parser.GetCurrent(), 0);
|
||||
@ -143,22 +141,14 @@ namespace LibHac.IO.Save
|
||||
int parentIndex = CreateParentDirectoryRecursive(ref parser, ref key);
|
||||
|
||||
int index = DirectoryTable.GetIndexFromKey(ref key).Index;
|
||||
var dirEntry = new TableEntry<SaveFindPosition>();
|
||||
TableEntry<SaveFindPosition> dirEntry = default;
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
index = DirectoryTable.Add(ref key, ref dirEntry);
|
||||
// Directory already exists. Do nothing.
|
||||
if (index >= 0) return;
|
||||
|
||||
DirectoryTable.GetValue(parentIndex, out TableEntry<SaveFindPosition> parentEntry);
|
||||
index = DirectoryTable.Add(ref key, ref dirEntry);
|
||||
|
||||
dirEntry.NextSibling = parentEntry.Value.NextDirectory;
|
||||
parentEntry.Value.NextDirectory = index;
|
||||
|
||||
DirectoryTable.SetValue(parentIndex, ref parentEntry);
|
||||
}
|
||||
|
||||
dirEntry.Value = dirInfo;
|
||||
DirectoryTable.SetValue(index, ref dirEntry);
|
||||
LinkDirectoryToParent(parentIndex, index);
|
||||
}
|
||||
|
||||
private int CreateParentDirectoryRecursive(ref PathParser parser, ref SaveEntryKey key)
|
||||
@ -176,13 +166,7 @@ namespace LibHac.IO.Save
|
||||
|
||||
if (prevIndex > 0)
|
||||
{
|
||||
DirectoryTable.GetValue(prevIndex, out TableEntry<SaveFindPosition> parentEntry);
|
||||
|
||||
newEntry.NextSibling = parentEntry.Value.NextDirectory;
|
||||
parentEntry.Value.NextDirectory = index;
|
||||
|
||||
DirectoryTable.SetValue(prevIndex, ref parentEntry);
|
||||
DirectoryTable.SetValue(index, ref newEntry);
|
||||
LinkDirectoryToParent(prevIndex, index);
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,26 +178,39 @@ namespace LibHac.IO.Save
|
||||
return prevIndex;
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
private void LinkFileToParent(int parentIndex, int fileIndex)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
ReadOnlySpan<byte> pathBytes = Util.GetUtf8Bytes(path);
|
||||
|
||||
FindPathRecursive(pathBytes, out SaveEntryKey key);
|
||||
int parentIndex = key.Parent;
|
||||
|
||||
DirectoryTable.GetValue(parentIndex, out TableEntry<SaveFindPosition> parentEntry);
|
||||
FileTable.GetValue(fileIndex, out TableEntry<SaveFileInfo> fileEntry);
|
||||
|
||||
int toDeleteIndex = FileTable.GetIndexFromKey(ref key).Index;
|
||||
if (toDeleteIndex < 0) throw new FileNotFoundException();
|
||||
fileEntry.NextSibling = parentEntry.Value.NextFile;
|
||||
parentEntry.Value.NextFile = fileIndex;
|
||||
|
||||
FileTable.GetValue(toDeleteIndex, out TableEntry<SaveFileInfo> toDeleteEntry);
|
||||
DirectoryTable.SetValue(parentIndex, ref parentEntry);
|
||||
FileTable.SetValue(fileIndex, ref fileEntry);
|
||||
}
|
||||
|
||||
if (parentEntry.Value.NextFile == toDeleteIndex)
|
||||
private void LinkDirectoryToParent(int parentIndex, int dirIndex)
|
||||
{
|
||||
DirectoryTable.GetValue(parentIndex, out TableEntry<SaveFindPosition> parentEntry);
|
||||
DirectoryTable.GetValue(dirIndex, out TableEntry<SaveFindPosition> dirEntry);
|
||||
|
||||
dirEntry.NextSibling = parentEntry.Value.NextDirectory;
|
||||
parentEntry.Value.NextDirectory = dirIndex;
|
||||
|
||||
DirectoryTable.SetValue(parentIndex, ref parentEntry);
|
||||
DirectoryTable.SetValue(dirIndex, ref dirEntry);
|
||||
}
|
||||
|
||||
private void UnlinkFileFromParent(int parentIndex, int fileIndex)
|
||||
{
|
||||
DirectoryTable.GetValue(parentIndex, out TableEntry<SaveFindPosition> parentEntry);
|
||||
FileTable.GetValue(fileIndex, out TableEntry<SaveFileInfo> fileEntry);
|
||||
|
||||
if (parentEntry.Value.NextFile == fileIndex)
|
||||
{
|
||||
parentEntry.Value.NextFile = toDeleteEntry.NextSibling;
|
||||
parentEntry.Value.NextFile = fileEntry.NextSibling;
|
||||
DirectoryTable.SetValue(parentIndex, ref parentEntry);
|
||||
FileTable.Remove(ref key);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -225,12 +222,10 @@ namespace LibHac.IO.Save
|
||||
{
|
||||
FileTable.GetValue(curIndex, out TableEntry<SaveFileInfo> curEntry);
|
||||
|
||||
if (curIndex == toDeleteIndex)
|
||||
if (curIndex == fileIndex)
|
||||
{
|
||||
prevEntry.NextSibling = curEntry.NextSibling;
|
||||
FileTable.SetValue(prevIndex, ref prevEntry);
|
||||
|
||||
FileTable.Remove(ref key);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -238,35 +233,17 @@ namespace LibHac.IO.Save
|
||||
prevEntry = curEntry;
|
||||
curIndex = prevEntry.NextSibling;
|
||||
}
|
||||
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
private void UnlinkDirectoryFromParent(int parentIndex, int dirIndex)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
ReadOnlySpan<byte> pathBytes = Util.GetUtf8Bytes(path);
|
||||
|
||||
FindPathRecursive(pathBytes, out SaveEntryKey key);
|
||||
int parentIndex = key.Parent;
|
||||
|
||||
DirectoryTable.GetValue(parentIndex, out TableEntry<SaveFindPosition> parentEntry);
|
||||
DirectoryTable.GetValue(dirIndex, out TableEntry<SaveFindPosition> dirEntry);
|
||||
|
||||
int toDeleteIndex = DirectoryTable.GetIndexFromKey(ref key).Index;
|
||||
if (toDeleteIndex < 0) throw new DirectoryNotFoundException();
|
||||
|
||||
DirectoryTable.GetValue(toDeleteIndex, out TableEntry<SaveFindPosition> toDeleteEntry);
|
||||
|
||||
if (toDeleteEntry.Value.NextDirectory != 0 || toDeleteEntry.Value.NextFile != 0)
|
||||
if (parentEntry.Value.NextDirectory == dirIndex)
|
||||
{
|
||||
throw new IOException("Directory is not empty.");
|
||||
}
|
||||
|
||||
if (parentEntry.Value.NextDirectory == toDeleteIndex)
|
||||
{
|
||||
parentEntry.Value.NextDirectory = toDeleteEntry.NextSibling;
|
||||
parentEntry.Value.NextDirectory = dirEntry.NextSibling;
|
||||
DirectoryTable.SetValue(parentIndex, ref parentEntry);
|
||||
DirectoryTable.Remove(ref key);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -278,12 +255,10 @@ namespace LibHac.IO.Save
|
||||
{
|
||||
DirectoryTable.GetValue(curIndex, out TableEntry<SaveFindPosition> curEntry);
|
||||
|
||||
if (curIndex == toDeleteIndex)
|
||||
if (curIndex == dirIndex)
|
||||
{
|
||||
prevEntry.NextSibling = curEntry.NextSibling;
|
||||
DirectoryTable.SetValue(prevIndex, ref prevEntry);
|
||||
|
||||
DirectoryTable.Remove(ref key);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -291,8 +266,112 @@ namespace LibHac.IO.Save
|
||||
prevEntry = curEntry;
|
||||
curIndex = prevEntry.NextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
throw new DirectoryNotFoundException();
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
ReadOnlySpan<byte> pathBytes = Util.GetUtf8Bytes(path);
|
||||
|
||||
FindPathRecursive(pathBytes, out SaveEntryKey key);
|
||||
int parentIndex = key.Parent;
|
||||
|
||||
int toDeleteIndex = FileTable.GetIndexFromKey(ref key).Index;
|
||||
if (toDeleteIndex < 0) throw new FileNotFoundException();
|
||||
|
||||
UnlinkFileFromParent(parentIndex, toDeleteIndex);
|
||||
|
||||
FileTable.Remove(ref key);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
ReadOnlySpan<byte> pathBytes = Util.GetUtf8Bytes(path);
|
||||
|
||||
FindPathRecursive(pathBytes, out SaveEntryKey key);
|
||||
int parentIndex = key.Parent;
|
||||
|
||||
int toDeleteIndex = DirectoryTable.GetIndexFromKey(ref key).Index;
|
||||
if (toDeleteIndex < 0) throw new DirectoryNotFoundException();
|
||||
|
||||
DirectoryTable.GetValue(toDeleteIndex, out TableEntry<SaveFindPosition> toDeleteEntry);
|
||||
|
||||
if (toDeleteEntry.Value.NextDirectory != 0 || toDeleteEntry.Value.NextFile != 0)
|
||||
{
|
||||
throw new IOException("Directory is not empty.");
|
||||
}
|
||||
|
||||
UnlinkDirectoryFromParent(parentIndex, toDeleteIndex);
|
||||
|
||||
DirectoryTable.Remove(ref key);
|
||||
}
|
||||
|
||||
public void RenameFile(string srcPath, string dstPath)
|
||||
{
|
||||
if (srcPath == dstPath || TryOpenFile(dstPath, out _) || TryOpenDirectory(dstPath, out _))
|
||||
{
|
||||
throw new IOException("Destination path already exists.");
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> oldPathBytes = Util.GetUtf8Bytes(srcPath);
|
||||
ReadOnlySpan<byte> newPathBytes = Util.GetUtf8Bytes(dstPath);
|
||||
|
||||
if (!FindPathRecursive(oldPathBytes, out SaveEntryKey oldKey))
|
||||
{
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
int fileIndex = FileTable.GetIndexFromKey(ref oldKey).Index;
|
||||
|
||||
if (!FindPathRecursive(newPathBytes, out SaveEntryKey newKey))
|
||||
{
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
if (oldKey.Parent != newKey.Parent)
|
||||
{
|
||||
UnlinkFileFromParent(oldKey.Parent, fileIndex);
|
||||
LinkFileToParent(newKey.Parent, fileIndex);
|
||||
}
|
||||
|
||||
FileTable.ChangeKey(ref oldKey, ref newKey);
|
||||
}
|
||||
|
||||
public void RenameDirectory(string srcPath, string dstPath)
|
||||
{
|
||||
if (srcPath == dstPath || TryOpenFile(dstPath, out _) || TryOpenDirectory(dstPath, out _))
|
||||
{
|
||||
throw new IOException(Messages.DestPathAlreadyExists);
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> oldPathBytes = Util.GetUtf8Bytes(srcPath);
|
||||
ReadOnlySpan<byte> newPathBytes = Util.GetUtf8Bytes(dstPath);
|
||||
|
||||
if (!FindPathRecursive(oldPathBytes, out SaveEntryKey oldKey))
|
||||
{
|
||||
throw new DirectoryNotFoundException();
|
||||
}
|
||||
|
||||
int dirIndex = DirectoryTable.GetIndexFromKey(ref oldKey).Index;
|
||||
|
||||
if (!FindPathRecursive(newPathBytes, out SaveEntryKey newKey))
|
||||
{
|
||||
throw new IOException(Messages.PartialPathNotFound);
|
||||
}
|
||||
|
||||
if (PathTools.IsSubPath(oldPathBytes, newPathBytes))
|
||||
{
|
||||
throw new IOException(Messages.DestPathIsSubPath);
|
||||
}
|
||||
|
||||
if (oldKey.Parent != newKey.Parent)
|
||||
{
|
||||
UnlinkDirectoryFromParent(oldKey.Parent, dirIndex);
|
||||
LinkDirectoryToParent(newKey.Parent, dirIndex);
|
||||
}
|
||||
|
||||
DirectoryTable.ChangeKey(ref oldKey, ref newKey);
|
||||
}
|
||||
|
||||
public bool TryOpenDirectory(string path, out SaveFindPosition position)
|
||||
|
@ -127,12 +127,18 @@ namespace LibHac.IO.Save
|
||||
|
||||
public void RenameDirectory(string srcPath, string dstPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
srcPath = PathTools.Normalize(srcPath);
|
||||
dstPath = PathTools.Normalize(dstPath);
|
||||
|
||||
FileTable.RenameDirectory(srcPath, dstPath);
|
||||
}
|
||||
|
||||
public void RenameFile(string srcPath, string dstPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
srcPath = PathTools.Normalize(srcPath);
|
||||
dstPath = PathTools.Normalize(dstPath);
|
||||
|
||||
FileTable.RenameFile(srcPath, dstPath);
|
||||
}
|
||||
|
||||
public bool DirectoryExists(string path)
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@ -232,6 +233,26 @@ namespace LibHac.IO.Save
|
||||
Free(index);
|
||||
}
|
||||
|
||||
public void ChangeKey(ref SaveEntryKey oldKey, ref SaveEntryKey newKey)
|
||||
{
|
||||
int index = GetIndexFromKey(ref oldKey).Index;
|
||||
int newIndex = GetIndexFromKey(ref newKey).Index;
|
||||
|
||||
if (index == -1) throw new KeyNotFoundException("Old key was not found.");
|
||||
if (newIndex != -1) throw new KeyNotFoundException("New key already exists.");
|
||||
|
||||
Span<byte> entryBytes = stackalloc byte[_sizeOfEntry];
|
||||
Span<byte> name = entryBytes.Slice(4, MaxNameLength);
|
||||
ref SaveFsEntry entry = ref GetEntryFromBytes(entryBytes);
|
||||
|
||||
ReadEntry(index, entryBytes);
|
||||
|
||||
entry.Parent = newKey.Parent;
|
||||
newKey.Name.CopyTo(name);
|
||||
|
||||
WriteEntry(index, entryBytes);
|
||||
}
|
||||
|
||||
public void TrimFreeEntries()
|
||||
{
|
||||
Span<byte> entryBytes = stackalloc byte[_sizeOfEntry];
|
||||
|
@ -1,4 +1,5 @@
|
||||
using LibHac.IO;
|
||||
using System;
|
||||
using LibHac.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace LibHac.Tests
|
||||
@ -20,7 +21,6 @@ namespace LibHac.Tests
|
||||
new object[] {"/../a/b/c/.", "/a/b/c"},
|
||||
new object[] {"/./a/b/c/.", "/a/b/c"},
|
||||
|
||||
|
||||
new object[] {"/a/b/c/", "/a/b/c/"},
|
||||
new object[] {"/a/./b/../c/", "/a/c/"},
|
||||
new object[] {"/./b/../c/", "/c/"},
|
||||
@ -37,6 +37,25 @@ namespace LibHac.Tests
|
||||
new object[] {"./a/b/c/.", "/a/b/c"},
|
||||
};
|
||||
|
||||
public static object[][] SubPathTestItems =
|
||||
{
|
||||
new object[] {"/", "/", false},
|
||||
new object[] {"/", "/a", true},
|
||||
new object[] {"/", "/a/", true},
|
||||
|
||||
new object[] {"/a/b/c", "/a/b/c/d", true},
|
||||
new object[] {"/a/b/c/", "/a/b/c/d", true},
|
||||
|
||||
new object[] {"/a/b/c", "/a/b/c", false},
|
||||
new object[] {"/a/b/c/", "/a/b/c/", false},
|
||||
new object[] {"/a/b/c/", "/a/b/c", false},
|
||||
new object[] {"/a/b/c", "/a/b/c/", false},
|
||||
|
||||
new object[] {"/a/b/c/", "/a/b/cdef", false},
|
||||
new object[] {"/a/b/c", "/a/b/cdef", false},
|
||||
new object[] {"/a/b/c/", "/a/b/cd", false},
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(NormalizedPathTestItems))]
|
||||
public static void NormalizePath(string path, string expected)
|
||||
@ -45,5 +64,14 @@ namespace LibHac.Tests
|
||||
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(SubPathTestItems))]
|
||||
public static void TestSubPath(string rootPath, string path, bool expected)
|
||||
{
|
||||
bool actual = PathTools.IsSubPath(rootPath.AsSpan(), path.AsSpan());
|
||||
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user