mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
More closely match original FS behavior in IFileSystem
This commit is contained in:
parent
6adcc8cce0
commit
6f1596ae5f
18 changed files with 707 additions and 114 deletions
|
@ -1,5 +1,6 @@
|
||||||
mode: ContinuousDeployment
|
mode: ContinuousDeployment
|
||||||
increment: Patch
|
increment: Patch
|
||||||
|
next-version: 0.5.0
|
||||||
branches:
|
branches:
|
||||||
master:
|
master:
|
||||||
tag: alpha
|
tag: alpha
|
|
@ -19,7 +19,7 @@ namespace LibHac.Fs
|
||||||
if (!BaseFs.DirectoryExists(WorkingDir))
|
if (!BaseFs.DirectoryExists(WorkingDir))
|
||||||
{
|
{
|
||||||
BaseFs.CreateDirectory(WorkingDir);
|
BaseFs.CreateDirectory(WorkingDir);
|
||||||
BaseFs.CreateDirectory(CommittedDir);
|
BaseFs.EnsureDirectoryExists(CommittedDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BaseFs.DirectoryExists(CommittedDir))
|
if (BaseFs.DirectoryExists(CommittedDir))
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace LibHac.Fs
|
||||||
{
|
{
|
||||||
if (IsDisposed) throw new ObjectDisposedException(null);
|
if (IsDisposed) throw new ObjectDisposedException(null);
|
||||||
|
|
||||||
if ((Mode & OpenMode.Read) == 0) ThrowHelper.ThrowResult(ResultFs.InvalidOpenModeOperation, "File does not allow reading.");
|
if ((Mode & OpenMode.Read) == 0) ThrowHelper.ThrowResult(ResultFs.InvalidOpenModeForRead, "File does not allow reading.");
|
||||||
if (span == null) throw new ArgumentNullException(nameof(span));
|
if (span == null) throw new ArgumentNullException(nameof(span));
|
||||||
if (offset < 0) ThrowHelper.ThrowResult(ResultFs.ValueOutOfRange, "Offset must be non-negative.");
|
if (offset < 0) ThrowHelper.ThrowResult(ResultFs.ValueOutOfRange, "Offset must be non-negative.");
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ namespace LibHac.Fs
|
||||||
{
|
{
|
||||||
if (IsDisposed) throw new ObjectDisposedException(null);
|
if (IsDisposed) throw new ObjectDisposedException(null);
|
||||||
|
|
||||||
if ((Mode & OpenMode.Write) == 0) ThrowHelper.ThrowResult(ResultFs.InvalidOpenModeOperation, "File does not allow writing.");
|
if ((Mode & OpenMode.Write) == 0) ThrowHelper.ThrowResult(ResultFs.InvalidOpenModeForWrite, "File does not allow writing.");
|
||||||
|
|
||||||
if (span == null) throw new ArgumentNullException(nameof(span));
|
if (span == null) throw new ArgumentNullException(nameof(span));
|
||||||
if (offset < 0) ThrowHelper.ThrowResult(ResultFs.ValueOutOfRange, "Offset must be non-negative.");
|
if (offset < 0) ThrowHelper.ThrowResult(ResultFs.ValueOutOfRange, "Offset must be non-negative.");
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace LibHac.Fs
|
||||||
|
|
||||||
if (entry.Type == DirectoryEntryType.Directory)
|
if (entry.Type == DirectoryEntryType.Directory)
|
||||||
{
|
{
|
||||||
destFs.CreateDirectory(subDstPath);
|
destFs.EnsureDirectoryExists(subDstPath);
|
||||||
IDirectory subSrcDir = sourceFs.OpenDirectory(subSrcPath, OpenDirectoryMode.All);
|
IDirectory subSrcDir = sourceFs.OpenDirectory(subSrcPath, OpenDirectoryMode.All);
|
||||||
IDirectory subDstDir = destFs.OpenDirectory(subDstPath, OpenDirectoryMode.All);
|
IDirectory subDstDir = destFs.OpenDirectory(subDstPath, OpenDirectoryMode.All);
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ namespace LibHac.Fs
|
||||||
|
|
||||||
if (entry.Type == DirectoryEntryType.File)
|
if (entry.Type == DirectoryEntryType.File)
|
||||||
{
|
{
|
||||||
destFs.CreateFile(subDstPath, entry.Size, options);
|
destFs.CreateOrOverwriteFile(subDstPath, entry.Size, options);
|
||||||
|
|
||||||
using (IFile srcFile = sourceFs.OpenFile(subSrcPath, OpenMode.Read))
|
using (IFile srcFile = sourceFs.OpenFile(subSrcPath, OpenMode.Read))
|
||||||
using (IFile dstFile = destFs.OpenFile(subDstPath, OpenMode.Write | OpenMode.Append))
|
using (IFile dstFile = destFs.OpenFile(subDstPath, OpenMode.Write | OpenMode.Append))
|
||||||
|
@ -209,6 +209,54 @@ namespace LibHac.Fs
|
||||||
{
|
{
|
||||||
return fs.GetEntryType(path) == DirectoryEntryType.File;
|
return fs.GetEntryType(path) == DirectoryEntryType.File;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void EnsureDirectoryExists(this IFileSystem fs, string path)
|
||||||
|
{
|
||||||
|
path = PathTools.Normalize(path);
|
||||||
|
if (fs.DirectoryExists(path)) return;
|
||||||
|
|
||||||
|
// Find the first subdirectory in the chain that doesn't exist
|
||||||
|
int i;
|
||||||
|
for (i = path.Length - 1; i > 0; i--)
|
||||||
|
{
|
||||||
|
if (path[i] == '/')
|
||||||
|
{
|
||||||
|
string subPath = path.Substring(0, i);
|
||||||
|
|
||||||
|
if (fs.DirectoryExists(subPath))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// path[i] will be a '/', so skip that character
|
||||||
|
i++;
|
||||||
|
|
||||||
|
for (; i < path.Length; i++)
|
||||||
|
{
|
||||||
|
if (path[i] == '/')
|
||||||
|
{
|
||||||
|
string subPath = path.Substring(0, i);
|
||||||
|
|
||||||
|
fs.CreateDirectory(subPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CreateOrOverwriteFile(this IFileSystem fs, string path, long size)
|
||||||
|
{
|
||||||
|
fs.CreateOrOverwriteFile(path, size, CreateFileOptions.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CreateOrOverwriteFile(this IFileSystem fs, string path, long size, CreateFileOptions options)
|
||||||
|
{
|
||||||
|
path = PathTools.Normalize(path);
|
||||||
|
|
||||||
|
if (fs.FileExists(path)) fs.DeleteFile(path);
|
||||||
|
|
||||||
|
fs.CreateFile(path, size, CreateFileOptions.None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
|
|
|
@ -509,7 +509,7 @@ namespace LibHac.Fs
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Result GetMountName(ReadOnlySpan<char> path, out ReadOnlySpan<char> mountName, out ReadOnlySpan<char> subPath)
|
internal static Result GetMountName(ReadOnlySpan<char> path, out ReadOnlySpan<char> mountName, out ReadOnlySpan<char> subPath)
|
||||||
{
|
{
|
||||||
int mountLen = 0;
|
int mountLen = 0;
|
||||||
int maxMountLen = Math.Min(path.Length, PathTools.MountNameLength);
|
int maxMountLen = Math.Min(path.Length, PathTools.MountNameLength);
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace LibHac.Fs
|
||||||
|
|
||||||
if (entry.Type == DirectoryEntryType.Directory)
|
if (entry.Type == DirectoryEntryType.Directory)
|
||||||
{
|
{
|
||||||
fs.CreateDirectory(subDstPath);
|
fs.EnsureDirectoryExists(subDstPath);
|
||||||
|
|
||||||
fs.CopyDirectory(subSrcPath, subDstPath, options, logger);
|
fs.CopyDirectory(subSrcPath, subDstPath, options, logger);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ namespace LibHac.Fs
|
||||||
if (entry.Type == DirectoryEntryType.File)
|
if (entry.Type == DirectoryEntryType.File)
|
||||||
{
|
{
|
||||||
logger?.LogMessage(subSrcPath);
|
logger?.LogMessage(subSrcPath);
|
||||||
fs.CreateFile(subDstPath, entry.Size, options);
|
fs.CreateOrOverwriteFile(subDstPath, entry.Size, options);
|
||||||
|
|
||||||
fs.CopyFile(subSrcPath, subDstPath, logger);
|
fs.CopyFile(subSrcPath, subDstPath, logger);
|
||||||
}
|
}
|
||||||
|
@ -109,5 +109,67 @@ namespace LibHac.Fs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool DirectoryExists(this FileSystemManager fs, string path)
|
||||||
|
{
|
||||||
|
return fs.GetEntryType(path) == DirectoryEntryType.Directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool FileExists(this FileSystemManager fs, string path)
|
||||||
|
{
|
||||||
|
return fs.GetEntryType(path) == DirectoryEntryType.File;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EnsureDirectoryExists(this FileSystemManager fs, string path)
|
||||||
|
{
|
||||||
|
path = PathTools.Normalize(path);
|
||||||
|
if (fs.DirectoryExists(path)) return;
|
||||||
|
|
||||||
|
PathTools.GetMountNameLength(path, out int mountNameLength).ThrowIfFailure();
|
||||||
|
|
||||||
|
// Find the first subdirectory in the path that doesn't exist
|
||||||
|
int i;
|
||||||
|
for (i = path.Length - 1; i > mountNameLength + 2; i--)
|
||||||
|
{
|
||||||
|
if (path[i] == '/')
|
||||||
|
{
|
||||||
|
string subPath = path.Substring(0, i);
|
||||||
|
|
||||||
|
if (fs.DirectoryExists(subPath))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// path[i] will be a '/', so skip that character
|
||||||
|
i++;
|
||||||
|
|
||||||
|
for (; i < path.Length; i++)
|
||||||
|
{
|
||||||
|
if (path[i] == '/')
|
||||||
|
{
|
||||||
|
string subPath = path.Substring(0, i);
|
||||||
|
|
||||||
|
fs.CreateDirectory(subPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.CreateDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CreateOrOverwriteFile(this FileSystemManager fs, string path, long size)
|
||||||
|
{
|
||||||
|
fs.CreateOrOverwriteFile(path, size, CreateFileOptions.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CreateOrOverwriteFile(this FileSystemManager fs, string path, long size, CreateFileOptions options)
|
||||||
|
{
|
||||||
|
path = PathTools.Normalize(path);
|
||||||
|
|
||||||
|
if (fs.FileExists(path)) fs.DeleteFile(path);
|
||||||
|
|
||||||
|
fs.CreateFile(path, size, CreateFileOptions.None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace LibHac.Fs
|
namespace LibHac.Fs
|
||||||
{
|
{
|
||||||
|
@ -12,7 +11,13 @@ namespace LibHac.Fs
|
||||||
/// Creates all directories and subdirectories in the specified path unless they already exist.
|
/// Creates all directories and subdirectories in the specified path unless they already exist.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The full path of the directory to create.</param>
|
/// <param name="path">The full path of the directory to create.</param>
|
||||||
/// <exception cref="IOException">An I/O error occurred while creating the directory.</exception>
|
/// <remarks>
|
||||||
|
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||||
|
///
|
||||||
|
/// The parent directory of the specified path does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||||
|
/// Specified path already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||||
|
/// Insufficient free space to create the directory: <see cref="ResultFs.InsufficientFreeSpace"/>
|
||||||
|
/// </remarks>
|
||||||
void CreateDirectory(string path);
|
void CreateDirectory(string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -22,39 +27,58 @@ namespace LibHac.Fs
|
||||||
/// <param name="size">The initial size of the created file.</param>
|
/// <param name="size">The initial size of the created file.</param>
|
||||||
/// <param name="options">Flags to control how the file is created.
|
/// <param name="options">Flags to control how the file is created.
|
||||||
/// Should usually be <see cref="CreateFileOptions.None"/></param>
|
/// Should usually be <see cref="CreateFileOptions.None"/></param>
|
||||||
/// <exception cref="IOException">An I/O error occurred while creating the file.</exception>
|
/// <remarks>
|
||||||
|
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||||
|
///
|
||||||
|
/// The parent directory of the specified path does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||||
|
/// Specified path already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||||
|
/// Insufficient free space to create the file: <see cref="ResultFs.InsufficientFreeSpace"/>
|
||||||
|
/// </remarks>
|
||||||
void CreateFile(string path, long size, CreateFileOptions options);
|
void CreateFile(string path, long size, CreateFileOptions options);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the specified directory.
|
/// Deletes the specified directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The full path of the directory to delete.</param>
|
/// <param name="path">The full path of the directory to delete.</param>
|
||||||
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
|
/// <remarks>
|
||||||
/// <exception cref="IOException">An I/O error occurred while deleting the directory.</exception>
|
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||||
|
///
|
||||||
|
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||||
|
/// The specified directory is not empty: <see cref="ResultFs.DirectoryNotEmpty"/>
|
||||||
|
/// </remarks>
|
||||||
void DeleteDirectory(string path);
|
void DeleteDirectory(string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the specified directory and any subdirectories and files in the directory.
|
/// Deletes the specified directory and any subdirectories and files in the directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The full path of the directory to delete.</param>
|
/// <param name="path">The full path of the directory to delete.</param>
|
||||||
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
|
/// <remarks>
|
||||||
/// <exception cref="IOException">An I/O error occurred while deleting the directory.</exception>
|
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||||
|
///
|
||||||
|
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||||
|
/// </remarks>
|
||||||
void DeleteDirectoryRecursively(string path);
|
void DeleteDirectoryRecursively(string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes any subdirectories and files in the specified directory.
|
/// Deletes any subdirectories and files in the specified directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The full path of the directory to clean.</param>
|
/// <param name="path">The full path of the directory to clean.</param>
|
||||||
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
|
/// <remarks>
|
||||||
/// <exception cref="IOException">An I/O error occurred while deleting the directory.</exception>
|
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||||
|
///
|
||||||
|
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||||
|
/// </remarks>
|
||||||
void CleanDirectoryRecursively(string path);
|
void CleanDirectoryRecursively(string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the specified file.
|
/// Deletes the specified file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The full path of the file to delete.</param>
|
/// <param name="path">The full path of the file to delete.</param>
|
||||||
/// <exception cref="FileNotFoundException">The specified file does not exist.</exception>
|
/// <remarks>
|
||||||
/// <exception cref="IOException">An I/O error occurred while deleting the file.</exception>
|
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||||
|
///
|
||||||
|
/// The specified path does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
|
||||||
|
/// </remarks>
|
||||||
void DeleteFile(string path);
|
void DeleteFile(string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -63,8 +87,11 @@ namespace LibHac.Fs
|
||||||
/// <param name="path">The directory's full path.</param>
|
/// <param name="path">The directory's full path.</param>
|
||||||
/// <param name="mode">Specifies which sub-entries should be enumerated.</param>
|
/// <param name="mode">Specifies which sub-entries should be enumerated.</param>
|
||||||
/// <returns>An <see cref="IDirectory"/> instance for the specified directory.</returns>
|
/// <returns>An <see cref="IDirectory"/> instance for the specified directory.</returns>
|
||||||
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
|
/// <remarks>
|
||||||
/// <exception cref="IOException">An I/O error occurred while opening the directory.</exception>
|
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||||
|
///
|
||||||
|
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||||
|
/// </remarks>
|
||||||
IDirectory OpenDirectory(string path, OpenDirectoryMode mode);
|
IDirectory OpenDirectory(string path, OpenDirectoryMode mode);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -73,8 +100,11 @@ namespace LibHac.Fs
|
||||||
/// <param name="path">The full path of the file to open.</param>
|
/// <param name="path">The full path of the file to open.</param>
|
||||||
/// <param name="mode">Specifies the access permissions of the created <see cref="IFile"/>.</param>
|
/// <param name="mode">Specifies the access permissions of the created <see cref="IFile"/>.</param>
|
||||||
/// <returns>An <see cref="IFile"/> instance for the specified path.</returns>
|
/// <returns>An <see cref="IFile"/> instance for the specified path.</returns>
|
||||||
/// <exception cref="FileNotFoundException">The specified file does not exist.</exception>
|
/// <remarks>
|
||||||
/// <exception cref="IOException">An I/O error occurred while deleting the file.</exception>
|
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||||
|
///
|
||||||
|
/// The specified path does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
|
||||||
|
/// </remarks>
|
||||||
IFile OpenFile(string path, OpenMode mode);
|
IFile OpenFile(string path, OpenMode mode);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -82,8 +112,16 @@ namespace LibHac.Fs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="srcPath">The full path of the directory to rename.</param>
|
/// <param name="srcPath">The full path of the directory to rename.</param>
|
||||||
/// <param name="dstPath">The new full path of the directory.</param>
|
/// <param name="dstPath">The new full path of the directory.</param>
|
||||||
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
|
/// <returns>An <see cref="IFile"/> instance for the specified path.</returns>
|
||||||
/// <exception cref="IOException">An I/O error occurred while deleting the directory.</exception>
|
/// <remarks>
|
||||||
|
/// If <paramref name="srcPath"/> and <paramref name="dstPath"/> are the same, this function does nothing and returns successfully.
|
||||||
|
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||||
|
///
|
||||||
|
/// <paramref name="srcPath"/> does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||||
|
/// <paramref name="dstPath"/>'s parent directory does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||||
|
/// <paramref name="dstPath"/> already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||||
|
/// Either <paramref name="srcPath"/> or <paramref name="dstPath"/> is a subpath of the other: <see cref="ResultFs.DestinationIsSubPathOfSource"/>
|
||||||
|
/// </remarks>
|
||||||
void RenameDirectory(string srcPath, string dstPath);
|
void RenameDirectory(string srcPath, string dstPath);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -91,7 +129,14 @@ namespace LibHac.Fs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="srcPath">The full path of the file to rename.</param>
|
/// <param name="srcPath">The full path of the file to rename.</param>
|
||||||
/// <param name="dstPath">The new full path of the file.</param>
|
/// <param name="dstPath">The new full path of the file.</param>
|
||||||
/// <exception cref="IOException">An I/O error occurred while deleting the file.</exception>
|
/// <remarks>
|
||||||
|
/// If <paramref name="srcPath"/> and <paramref name="dstPath"/> are the same, this function does nothing and returns successfully.
|
||||||
|
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||||
|
///
|
||||||
|
/// <paramref name="srcPath"/> does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
|
||||||
|
/// <paramref name="dstPath"/>'s parent directory does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||||
|
/// <paramref name="dstPath"/> already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||||
|
/// </remarks>
|
||||||
void RenameFile(string srcPath, string dstPath);
|
void RenameFile(string srcPath, string dstPath);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -99,6 +144,11 @@ namespace LibHac.Fs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The full path to check.</param>
|
/// <param name="path">The full path to check.</param>
|
||||||
/// <returns>The <see cref="DirectoryEntryType"/> of the file.</returns>
|
/// <returns>The <see cref="DirectoryEntryType"/> of the file.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This function operates slightly differently than it does in Horizon OS.
|
||||||
|
/// Instead of returning <see cref="ResultFs.PathNotFound"/> when an entry is missing,
|
||||||
|
/// the function will return <see cref="DirectoryEntryType.NotFound"/>.
|
||||||
|
/// </remarks>
|
||||||
DirectoryEntryType GetEntryType(string path);
|
DirectoryEntryType GetEntryType(string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -121,6 +171,11 @@ namespace LibHac.Fs
|
||||||
/// <param name="path">The path of the file or directory.</param>
|
/// <param name="path">The path of the file or directory.</param>
|
||||||
/// <returns>The timestamps for the specified file or directory.
|
/// <returns>The timestamps for the specified file or directory.
|
||||||
/// This value is expressed as a Unix timestamp</returns>
|
/// This value is expressed as a Unix timestamp</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||||
|
///
|
||||||
|
/// The specified path does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||||
|
/// </remarks>
|
||||||
FileTimeStampRaw GetFileTimeStampRaw(string path);
|
FileTimeStampRaw GetFileTimeStampRaw(string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
@ -20,8 +21,22 @@ namespace LibHac.Fs
|
||||||
LocalPath = fs.ResolveLocalPath(path);
|
LocalPath = fs.ResolveLocalPath(path);
|
||||||
Mode = mode;
|
Mode = mode;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
DirInfo = new DirectoryInfo(LocalPath);
|
DirInfo = new DirectoryInfo(LocalPath);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException ||
|
||||||
|
ex is PathTooLongException)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DirInfo.Exists)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<DirectoryEntry> Read()
|
public IEnumerable<DirectoryEntry> Read()
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,15 +5,16 @@ namespace LibHac.Fs
|
||||||
{
|
{
|
||||||
public class LocalFile : FileBase
|
public class LocalFile : FileBase
|
||||||
{
|
{
|
||||||
private string Path { get; }
|
private const int ErrorHandleDiskFull = unchecked((int)0x80070027);
|
||||||
|
private const int ErrorDiskFull = unchecked((int)0x80070070);
|
||||||
|
|
||||||
private FileStream Stream { get; }
|
private FileStream Stream { get; }
|
||||||
private StreamFile File { get; }
|
private StreamFile File { get; }
|
||||||
|
|
||||||
public LocalFile(string path, OpenMode mode)
|
public LocalFile(string path, OpenMode mode)
|
||||||
{
|
{
|
||||||
Path = path;
|
|
||||||
Mode = mode;
|
Mode = mode;
|
||||||
Stream = new FileStream(Path, FileMode.Open, GetFileAccess(mode), GetFileShare(mode));
|
Stream = OpenFile(path, mode);
|
||||||
File = new StreamFile(Stream, mode);
|
File = new StreamFile(Stream, mode);
|
||||||
|
|
||||||
ToDispose.Add(File);
|
ToDispose.Add(File);
|
||||||
|
@ -47,9 +48,17 @@ namespace LibHac.Fs
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetSize(long size)
|
public override void SetSize(long size)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
File.SetSize(size);
|
File.SetSize(size);
|
||||||
}
|
}
|
||||||
|
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.InsufficientFreeSpace, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static FileAccess GetFileAccess(OpenMode mode)
|
private static FileAccess GetFileAccess(OpenMode mode)
|
||||||
{
|
{
|
||||||
|
@ -61,5 +70,25 @@ namespace LibHac.Fs
|
||||||
{
|
{
|
||||||
return mode.HasFlag(OpenMode.Write) ? FileShare.Read : FileShare.ReadWrite;
|
return mode.HasFlag(OpenMode.Write) ? FileShare.Read : FileShare.ReadWrite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static FileStream OpenFile(string path, OpenMode mode)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new FileStream(path, FileMode.Open, GetFileAccess(mode), GetFileShare(mode));
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException ||
|
||||||
|
ex is PathTooLongException || ex is DirectoryNotFoundException ||
|
||||||
|
ex is FileNotFoundException || ex is NotSupportedException)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
// todo: Should a HorizonResultException be thrown?
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
namespace LibHac.Fs
|
namespace LibHac.Fs
|
||||||
{
|
{
|
||||||
public class LocalFileSystem : IAttributeFileSystem
|
public class LocalFileSystem : IAttributeFileSystem
|
||||||
{
|
{
|
||||||
|
private const int ErrorHandleDiskFull = unchecked((int)0x80070027);
|
||||||
|
private const int ErrorFileExists = unchecked((int)0x80070050);
|
||||||
|
private const int ErrorDiskFull = unchecked((int)0x80070070);
|
||||||
|
private const int ErrorDirNotEmpty = unchecked((int)0x80070091);
|
||||||
|
|
||||||
private string BasePath { get; }
|
private string BasePath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -46,81 +52,116 @@ namespace LibHac.Fs
|
||||||
|
|
||||||
public long GetFileSize(string path)
|
public long GetFileSize(string path)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||||
var info = new FileInfo(ResolveLocalPath(path));
|
|
||||||
return info.Length;
|
FileInfo info = GetFileInfo(localPath);
|
||||||
|
return GetSizeInternal(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateDirectory(string path)
|
public void CreateDirectory(string path)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||||
Directory.CreateDirectory(ResolveLocalPath(path));
|
|
||||||
|
DirectoryInfo dir = GetDirInfo(localPath);
|
||||||
|
|
||||||
|
if (dir.Exists)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dir.Parent?.Exists != true)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateDirInternal(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateFile(string path, long size, CreateFileOptions options)
|
public void CreateFile(string path, long size, CreateFileOptions options)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||||
string localPath = ResolveLocalPath(path);
|
|
||||||
string localDir = ResolveLocalPath(PathTools.GetParentDirectory(path));
|
|
||||||
|
|
||||||
if (localDir != null) Directory.CreateDirectory(localDir);
|
FileInfo file = GetFileInfo(localPath);
|
||||||
|
|
||||||
using (FileStream stream = File.Create(localPath))
|
if (file.Exists)
|
||||||
{
|
{
|
||||||
stream.SetLength(size);
|
ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.Directory?.Exists != true)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (FileStream stream = CreateFileInternal(file))
|
||||||
|
{
|
||||||
|
SetStreamLengthInternal(stream, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteDirectory(string path)
|
public void DeleteDirectory(string path)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||||
|
|
||||||
Directory.Delete(ResolveLocalPath(path));
|
DirectoryInfo dir = GetDirInfo(localPath);
|
||||||
|
|
||||||
|
DeleteDirectoryInternal(dir, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteDirectoryRecursively(string path)
|
public void DeleteDirectoryRecursively(string path)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||||
|
|
||||||
Directory.Delete(ResolveLocalPath(path), true);
|
DirectoryInfo dir = GetDirInfo(localPath);
|
||||||
|
|
||||||
|
DeleteDirectoryInternal(dir, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CleanDirectoryRecursively(string path)
|
public void CleanDirectoryRecursively(string path)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||||
string localPath = ResolveLocalPath(path);
|
|
||||||
|
|
||||||
foreach (string file in Directory.EnumerateFiles(localPath))
|
foreach (string file in Directory.EnumerateFiles(localPath))
|
||||||
{
|
{
|
||||||
File.Delete(file);
|
DeleteFileInternal(GetFileInfo(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (string dir in Directory.EnumerateDirectories(localPath))
|
foreach (string dir in Directory.EnumerateDirectories(localPath))
|
||||||
{
|
{
|
||||||
Directory.Delete(dir, true);
|
DeleteDirectoryInternal(GetDirInfo(dir), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteFile(string path)
|
public void DeleteFile(string path)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||||
|
|
||||||
string resolveLocalPath = ResolveLocalPath(path);
|
FileInfo file = GetFileInfo(localPath);
|
||||||
File.Delete(resolveLocalPath);
|
|
||||||
|
DeleteFileInternal(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
|
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
path = PathTools.Normalize(path);
|
||||||
|
|
||||||
|
if (GetEntryType(path) == DirectoryEntryType.File)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
return new LocalDirectory(this, path, mode);
|
return new LocalDirectory(this, path, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IFile OpenFile(string path, OpenMode mode)
|
public IFile OpenFile(string path, OpenMode mode)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||||
|
|
||||||
|
if (GetEntryType(path) == DirectoryEntryType.Directory)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
string localPath = ResolveLocalPath(path);
|
|
||||||
return new LocalFile(localPath, mode);
|
return new LocalFile(localPath, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,38 +170,49 @@ namespace LibHac.Fs
|
||||||
srcPath = PathTools.Normalize(srcPath);
|
srcPath = PathTools.Normalize(srcPath);
|
||||||
dstPath = PathTools.Normalize(dstPath);
|
dstPath = PathTools.Normalize(dstPath);
|
||||||
|
|
||||||
string srcLocalPath = ResolveLocalPath(srcPath);
|
// Official FS behavior is to do nothing in this case
|
||||||
string dstLocalPath = ResolveLocalPath(dstPath);
|
if (srcPath == dstPath) return;
|
||||||
|
|
||||||
string directoryName = ResolveLocalPath(PathTools.GetParentDirectory(dstPath));
|
// FS does the subpath check before verifying the path exists
|
||||||
if (directoryName != null) Directory.CreateDirectory(directoryName);
|
if (PathTools.IsSubPath(srcPath.AsSpan(), dstPath.AsSpan()))
|
||||||
Directory.Move(srcLocalPath, dstLocalPath);
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.DestinationIsSubPathOfSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectoryInfo srcDir = GetDirInfo(ResolveLocalPath(srcPath));
|
||||||
|
DirectoryInfo dstDir = GetDirInfo(ResolveLocalPath(dstPath));
|
||||||
|
|
||||||
|
RenameDirInternal(srcDir, dstDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RenameFile(string srcPath, string dstPath)
|
public void RenameFile(string srcPath, string dstPath)
|
||||||
{
|
{
|
||||||
srcPath = PathTools.Normalize(srcPath);
|
string srcLocalPath = ResolveLocalPath(PathTools.Normalize(srcPath));
|
||||||
dstPath = PathTools.Normalize(dstPath);
|
string dstLocalPath = ResolveLocalPath(PathTools.Normalize(dstPath));
|
||||||
|
|
||||||
string srcLocalPath = ResolveLocalPath(srcPath);
|
// Official FS behavior is to do nothing in this case
|
||||||
string dstLocalPath = ResolveLocalPath(dstPath);
|
if (srcLocalPath == dstLocalPath) return;
|
||||||
string dstLocalDir = ResolveLocalPath(PathTools.GetParentDirectory(dstPath));
|
|
||||||
|
|
||||||
if (dstLocalDir != null) Directory.CreateDirectory(dstLocalDir);
|
FileInfo srcFile = GetFileInfo(srcLocalPath);
|
||||||
File.Move(srcLocalPath, dstLocalPath);
|
FileInfo dstFile = GetFileInfo(dstLocalPath);
|
||||||
|
|
||||||
|
RenameFileInternal(srcFile, dstFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DirectoryEntryType GetEntryType(string path)
|
public DirectoryEntryType GetEntryType(string path)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||||
string localPath = ResolveLocalPath(path);
|
|
||||||
|
|
||||||
if (Directory.Exists(localPath))
|
DirectoryInfo dir = GetDirInfo(localPath);
|
||||||
|
|
||||||
|
if (dir.Exists)
|
||||||
{
|
{
|
||||||
return DirectoryEntryType.Directory;
|
return DirectoryEntryType.Directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (File.Exists(localPath))
|
FileInfo file = GetFileInfo(localPath);
|
||||||
|
|
||||||
|
if (file.Exists)
|
||||||
{
|
{
|
||||||
return DirectoryEntryType.File;
|
return DirectoryEntryType.File;
|
||||||
}
|
}
|
||||||
|
@ -170,8 +222,9 @@ namespace LibHac.Fs
|
||||||
|
|
||||||
public FileTimeStampRaw GetFileTimeStampRaw(string path)
|
public FileTimeStampRaw GetFileTimeStampRaw(string path)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||||
string localPath = ResolveLocalPath(path);
|
|
||||||
|
if (!GetFileInfo(localPath).Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||||
|
|
||||||
FileTimeStampRaw timeStamp = default;
|
FileTimeStampRaw timeStamp = default;
|
||||||
|
|
||||||
|
@ -196,5 +249,232 @@ namespace LibHac.Fs
|
||||||
|
|
||||||
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) =>
|
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) =>
|
||||||
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
|
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
|
||||||
|
|
||||||
|
private static long GetSizeInternal(FileInfo file)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return file.Length;
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException ex)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is SecurityException || ex is UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
// todo: Should a HorizonResultException be thrown?
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FileStream CreateFileInternal(FileInfo file)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new FileStream(file.FullName, FileMode.CreateNew, FileAccess.ReadWrite);
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException ex)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.InsufficientFreeSpace, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (IOException ex) when (ex.HResult == ErrorFileExists)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is SecurityException || ex is UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
// todo: Should a HorizonResultException be thrown?
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetStreamLengthInternal(Stream stream, long size)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
stream.SetLength(size);
|
||||||
|
}
|
||||||
|
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.InsufficientFreeSpace, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DeleteDirectoryInternal(DirectoryInfo dir, bool recursive)
|
||||||
|
{
|
||||||
|
if (!dir.Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
dir.Delete(recursive);
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException ex)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (IOException ex) when (ex.HResult == ErrorDirNotEmpty)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.DirectoryNotEmpty, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
// todo: Should a HorizonResultException be thrown?
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureDeleted(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DeleteFileInternal(FileInfo file)
|
||||||
|
{
|
||||||
|
if (!file.Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
file.Delete();
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException ex)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (IOException ex) when (ex.HResult == ErrorDirNotEmpty)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.DirectoryNotEmpty, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
// todo: Should a HorizonResultException be thrown?
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureDeleted(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CreateDirInternal(DirectoryInfo dir)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
dir.Create();
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException ex)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.InsufficientFreeSpace, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is SecurityException || ex is UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
// todo: Should a HorizonResultException be thrown?
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenameDirInternal(DirectoryInfo source, DirectoryInfo dest)
|
||||||
|
{
|
||||||
|
if (!source.Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||||
|
if (dest.Exists) ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
source.MoveTo(dest.FullName);
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException ex)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
// todo: Should a HorizonResultException be thrown?
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenameFileInternal(FileInfo source, FileInfo dest)
|
||||||
|
{
|
||||||
|
if (!source.Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||||
|
if (dest.Exists) ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
source.MoveTo(dest.FullName);
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException ex)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
// todo: Should a HorizonResultException be thrown?
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// GetFileInfo and GetDirInfo detect invalid paths
|
||||||
|
private static FileInfo GetFileInfo(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new FileInfo(path);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException ||
|
||||||
|
ex is PathTooLongException)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DirectoryInfo GetDirInfo(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new DirectoryInfo(path);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException ||
|
||||||
|
ex is PathTooLongException)
|
||||||
|
{
|
||||||
|
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete operations on IFileSystem should be synchronous
|
||||||
|
// DeleteFile and RemoveDirectory only mark the file for deletion, so we need
|
||||||
|
// to poll the filesystem until it's actually gone
|
||||||
|
private static void EnsureDeleted(FileSystemInfo entry)
|
||||||
|
{
|
||||||
|
int tries = 0;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
entry.Refresh();
|
||||||
|
tries++;
|
||||||
|
|
||||||
|
if (tries > 1000)
|
||||||
|
{
|
||||||
|
throw new IOException($"Unable to delete file {entry.FullName}");
|
||||||
|
}
|
||||||
|
} while (entry.Exists);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -194,16 +194,24 @@ namespace LibHac.Fs
|
||||||
|
|
||||||
public static string GetParentDirectory(string path)
|
public static string GetParentDirectory(string path)
|
||||||
{
|
{
|
||||||
if (path.Length == 0) return "/";
|
Debug.Assert(IsNormalized(path.AsSpan()));
|
||||||
|
|
||||||
int i = path.Length - 1;
|
int i = path.Length - 1;
|
||||||
|
|
||||||
|
// Handles non-mounted root paths
|
||||||
|
if (i == 0) return string.Empty;
|
||||||
|
|
||||||
// A trailing separator should be ignored
|
// A trailing separator should be ignored
|
||||||
if (path[i] == '/') i--;
|
if (path[i] == '/') i--;
|
||||||
|
|
||||||
|
// Handles mounted root paths
|
||||||
|
if (i >= 0 && path[i] == ':') return string.Empty;
|
||||||
|
|
||||||
while (i >= 0 && path[i] != '/') i--;
|
while (i >= 0 && path[i] != '/') i--;
|
||||||
|
|
||||||
if (i < 1) return "/";
|
// Leave the '/' if the parent is the root directory
|
||||||
|
if (i == 0 || i > 0 && path[i - 1] == ':') i++;
|
||||||
|
|
||||||
return path.Substring(0, i);
|
return path.Substring(0, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,50 +304,74 @@ namespace LibHac.Fs
|
||||||
return state == NormalizeState.Normal || state == NormalizeState.Delimiter;
|
return state == NormalizeState.Normal || state == NormalizeState.Delimiter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsSubPath(ReadOnlySpan<char> rootPath, ReadOnlySpan<char> path)
|
/// <summary>
|
||||||
|
/// Checks if either of the 2 paths is a sub-path of the other. Input paths must be normalized.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path1">The first path to be compared.</param>
|
||||||
|
/// <param name="path2">The second path to be compared.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsSubPath(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2)
|
||||||
{
|
{
|
||||||
Debug.Assert(IsNormalized(rootPath));
|
Debug.Assert(IsNormalized(path1));
|
||||||
Debug.Assert(IsNormalized(path));
|
Debug.Assert(IsNormalized(path2));
|
||||||
|
|
||||||
if (path.Length <= rootPath.Length) return false;
|
if (path1.Length == 0 || path2.Length == 0) return true;
|
||||||
|
|
||||||
for (int i = 0; i < rootPath.Length; i++)
|
//Ignore any trailing slashes
|
||||||
|
if (path1[path1.Length - 1] == DirectorySeparator)
|
||||||
{
|
{
|
||||||
if (rootPath[i] != path[i]) return false;
|
path1 = path1.Slice(0, path1.Length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The input root path might or might not have a trailing slash.
|
if (path2[path2.Length - 1] == DirectorySeparator)
|
||||||
// Both are treated the same.
|
{
|
||||||
int rootLength = rootPath[rootPath.Length - 1] == DirectorySeparator
|
path2 = path2.Slice(0, path2.Length - 1);
|
||||||
? 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)
|
ReadOnlySpan<char> shortPath = path1.Length < path2.Length ? path1 : path2;
|
||||||
{
|
ReadOnlySpan<char> longPath = path1.Length < path2.Length ? path2 : path1;
|
||||||
Debug.Assert(IsNormalized(rootPath));
|
|
||||||
Debug.Assert(IsNormalized(path));
|
|
||||||
|
|
||||||
if (path.Length <= rootPath.Length) return false;
|
if (!shortPath.SequenceEqual(longPath.Slice(0, shortPath.Length)))
|
||||||
|
|
||||||
for (int i = 0; i < rootPath.Length; i++)
|
|
||||||
{
|
{
|
||||||
if (rootPath[i] != path[i]) return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The input root path might or might not have a trailing slash.
|
return longPath.Length > shortPath.Length + 1 && longPath[shortPath.Length] == DirectorySeparator;
|
||||||
// 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,
|
/// <summary>
|
||||||
// and if the possible sub path continues past that point.
|
/// Checks if either of the 2 paths is a sub-path of the other. Input paths must be normalized.
|
||||||
return path[rootLength] == DirectorySeparator && path.Length > rootLength + 1;
|
/// </summary>
|
||||||
|
/// <param name="path1">The first path to be compared.</param>
|
||||||
|
/// <param name="path2">The second path to be compared.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsSubPath(ReadOnlySpan<byte> path1, ReadOnlySpan<byte> path2)
|
||||||
|
{
|
||||||
|
Debug.Assert(IsNormalized(path1));
|
||||||
|
Debug.Assert(IsNormalized(path2));
|
||||||
|
|
||||||
|
if (path1.Length == 0 || path2.Length == 0) return true;
|
||||||
|
|
||||||
|
//Ignore any trailing slashes
|
||||||
|
if (path1[path1.Length - 1] == DirectorySeparator)
|
||||||
|
{
|
||||||
|
path1 = path1.Slice(0, path1.Length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path2[path2.Length - 1] == DirectorySeparator)
|
||||||
|
{
|
||||||
|
path2 = path2.Slice(0, path2.Length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> shortPath = path1.Length < path2.Length ? path1 : path2;
|
||||||
|
ReadOnlySpan<byte> longPath = path1.Length < path2.Length ? path2 : path1;
|
||||||
|
|
||||||
|
if (!shortPath.SequenceEqual(longPath.Slice(0, shortPath.Length)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return longPath.Length > shortPath.Length + 1 && longPath[shortPath.Length] == DirectorySeparator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Combine(string path1, string path2)
|
public static string Combine(string path1, string path2)
|
||||||
|
@ -366,6 +398,20 @@ namespace LibHac.Fs
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result GetMountName(string path, out string mountName)
|
public static Result GetMountName(string path, out string mountName)
|
||||||
|
{
|
||||||
|
Result rc = GetMountNameLength(path, out int length);
|
||||||
|
|
||||||
|
if (rc.IsFailure())
|
||||||
|
{
|
||||||
|
mountName = default;
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
mountName = path.Substring(0, length);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result GetMountNameLength(string path, out int length)
|
||||||
{
|
{
|
||||||
int maxLen = Math.Min(path.Length, MountNameLength);
|
int maxLen = Math.Min(path.Length, MountNameLength);
|
||||||
|
|
||||||
|
@ -373,12 +419,12 @@ namespace LibHac.Fs
|
||||||
{
|
{
|
||||||
if (path[i] == MountSeparator)
|
if (path[i] == MountSeparator)
|
||||||
{
|
{
|
||||||
mountName = path.Substring(0, i);
|
length = i;
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mountName = default;
|
length = default;
|
||||||
return ResultFs.InvalidMountName;
|
return ResultFs.InvalidMountName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
public static Result DirectoryUnobtainable => new Result(ModuleFs, 6006);
|
public static Result DirectoryUnobtainable => new Result(ModuleFs, 6006);
|
||||||
public static Result NotNormalized => new Result(ModuleFs, 6007);
|
public static Result NotNormalized => new Result(ModuleFs, 6007);
|
||||||
|
|
||||||
|
public static Result DestinationIsSubPathOfSource => new Result(ModuleFs, 6032);
|
||||||
public static Result PathNotFoundInSaveDataFileTable => new Result(ModuleFs, 6033);
|
public static Result PathNotFoundInSaveDataFileTable => new Result(ModuleFs, 6033);
|
||||||
public static Result DifferentDestFileSystem => new Result(ModuleFs, 6034);
|
public static Result DifferentDestFileSystem => new Result(ModuleFs, 6034);
|
||||||
public static Result InvalidOffset => new Result(ModuleFs, 6061);
|
public static Result InvalidOffset => new Result(ModuleFs, 6061);
|
||||||
|
@ -79,6 +80,8 @@
|
||||||
|
|
||||||
public static Result InvalidOpenModeOperation => new Result(ModuleFs, 6200);
|
public static Result InvalidOpenModeOperation => new Result(ModuleFs, 6200);
|
||||||
public static Result AllowAppendRequiredForImplicitExtension => new Result(ModuleFs, 6201);
|
public static Result AllowAppendRequiredForImplicitExtension => new Result(ModuleFs, 6201);
|
||||||
|
public static Result InvalidOpenModeForRead => new Result(ModuleFs, 6202);
|
||||||
|
public static Result InvalidOpenModeForWrite => new Result(ModuleFs, 6203);
|
||||||
|
|
||||||
public static Result UnsupportedOperation => new Result(ModuleFs, 6300);
|
public static Result UnsupportedOperation => new Result(ModuleFs, 6300);
|
||||||
public static Result UnsupportedOperationInMemoryStorageSetSize => new Result(ModuleFs, 6316);
|
public static Result UnsupportedOperationInMemoryStorageSetSize => new Result(ModuleFs, 6316);
|
||||||
|
|
|
@ -362,7 +362,7 @@ namespace LibHac.Fs.Save
|
||||||
|
|
||||||
if (PathTools.IsSubPath(oldPathBytes, newPathBytes))
|
if (PathTools.IsSubPath(oldPathBytes, newPathBytes))
|
||||||
{
|
{
|
||||||
throw new IOException(Messages.DestPathIsSubPath);
|
ThrowHelper.ThrowResult(ResultFs.DestinationIsSubPathOfSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldKey.Parent != newKey.Parent)
|
if (oldKey.Parent != newKey.Parent)
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
<RepositoryUrl>https://github.com/Thealexbarney/LibHac</RepositoryUrl>
|
<RepositoryUrl>https://github.com/Thealexbarney/LibHac</RepositoryUrl>
|
||||||
|
|
||||||
<VersionPrefix>0.4.1</VersionPrefix>
|
<VersionPrefix>0.5.0</VersionPrefix>
|
||||||
<PathMap Condition=" '$(BuildType)' == 'Release' ">$(MSBuildProjectDirectory)=C:/LibHac/</PathMap>
|
<PathMap Condition=" '$(BuildType)' == 'Release' ">$(MSBuildProjectDirectory)=C:/LibHac/</PathMap>
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
namespace LibHac
|
using System;
|
||||||
|
|
||||||
|
namespace LibHac
|
||||||
{
|
{
|
||||||
internal static class ThrowHelper
|
internal static class ThrowHelper
|
||||||
{
|
{
|
||||||
public static void ThrowResult(Result result) => throw new HorizonResultException(result);
|
public static void ThrowResult(Result result) => throw new HorizonResultException(result);
|
||||||
public static void ThrowResult(Result result, string message) => throw new HorizonResultException(result, message);
|
|
||||||
|
public static void ThrowResult(Result result, Exception innerException) =>
|
||||||
|
throw new HorizonResultException(result, string.Empty, innerException);
|
||||||
|
|
||||||
|
public static void ThrowResult(Result result, string message) =>
|
||||||
|
throw new HorizonResultException(result, message);
|
||||||
|
|
||||||
|
public static void ThrowResult(Result result, string message, Exception innerException) =>
|
||||||
|
throw new HorizonResultException(result, message, innerException);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ namespace hactoolnet
|
||||||
|
|
||||||
if (entry.Type == DirectoryEntryType.Directory)
|
if (entry.Type == DirectoryEntryType.Directory)
|
||||||
{
|
{
|
||||||
fs.CreateDirectory(subDstPath);
|
fs.EnsureDirectoryExists(subDstPath);
|
||||||
|
|
||||||
CopyDirectoryWithProgressInternal(fs, subSrcPath, subDstPath, options, logger);
|
CopyDirectoryWithProgressInternal(fs, subSrcPath, subDstPath, options, logger);
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ namespace hactoolnet
|
||||||
if (entry.Type == DirectoryEntryType.File)
|
if (entry.Type == DirectoryEntryType.File)
|
||||||
{
|
{
|
||||||
logger?.LogMessage(subSrcPath);
|
logger?.LogMessage(subSrcPath);
|
||||||
fs.CreateFile(subDstPath, entry.Size, options);
|
fs.CreateOrOverwriteFile(subDstPath, entry.Size, options);
|
||||||
|
|
||||||
CopyFileWithProgress(fs, subSrcPath, subDstPath, logger);
|
CopyFileWithProgress(fs, subSrcPath, subDstPath, logger);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<VersionPrefix>0.4.1</VersionPrefix>
|
<VersionPrefix>0.5.0</VersionPrefix>
|
||||||
<PathMap Condition=" '$(BuildType)' == 'Release' ">$(MSBuildProjectDirectory)=C:/hactoolnet/</PathMap>
|
<PathMap Condition=" '$(BuildType)' == 'Release' ">$(MSBuildProjectDirectory)=C:/hactoolnet/</PathMap>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,32 @@ namespace LibHac.Tests
|
||||||
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/cdef", false},
|
new object[] {"/a/b/c", "/a/b/cdef", false},
|
||||||
new object[] {"/a/b/c/", "/a/b/cd", false},
|
new object[] {"/a/b/c/", "/a/b/cd", false},
|
||||||
|
|
||||||
|
new object[] {"mount:/", "mount:/", false},
|
||||||
|
new object[] {"mount:/", "mount:/a", true},
|
||||||
|
new object[] {"mount:/", "mount:/a/", true},
|
||||||
|
|
||||||
|
new object[] {"mount:/a/b/c", "mount:/a/b/c/d", true},
|
||||||
|
new object[] {"mount:/a/b/c/", "mount:/a/b/c/d", true},
|
||||||
|
|
||||||
|
new object[] {"mount:/a/b/c", "mount:/a/b/c", false},
|
||||||
|
new object[] {"mount:/a/b/c/", "mount:/a/b/c/", false},
|
||||||
|
new object[] {"mount:/a/b/c/", "mount:/a/b/c", false},
|
||||||
|
new object[] {"mount:/a/b/c", "mount:/a/b/c/", false},
|
||||||
|
|
||||||
|
new object[] {"mount:/a/b/c/", "mount:/a/b/cdef", false},
|
||||||
|
new object[] {"mount:/a/b/c", "mount:/a/b/cdef", false},
|
||||||
|
new object[] { "mount:/a/b/c/", "mount:/a/b/cd", false},
|
||||||
|
};
|
||||||
|
|
||||||
|
public static object[][] ParentDirectoryTestItems =
|
||||||
|
{
|
||||||
|
new object[] {"/", ""},
|
||||||
|
new object[] {"/a", "/"},
|
||||||
|
new object[] {"/aa/aabc/f", "/aa/aabc"},
|
||||||
|
new object[] {"mount:/", ""},
|
||||||
|
new object[] {"mount:/a", "mount:/"},
|
||||||
|
new object[] {"mount:/aa/aabc/f", "mount:/aa/aabc"}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static object[][] IsNormalizedTestItems = GetNormalizedPaths(true);
|
public static object[][] IsNormalizedTestItems = GetNormalizedPaths(true);
|
||||||
|
@ -107,6 +133,24 @@ namespace LibHac.Tests
|
||||||
Assert.Equal(expected, actual);
|
Assert.Equal(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(SubPathTestItems))]
|
||||||
|
public static void TestSubPathReverse(string rootPath, string path, bool expected)
|
||||||
|
{
|
||||||
|
bool actual = PathTools.IsSubPath(path.AsSpan(), rootPath.AsSpan());
|
||||||
|
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(ParentDirectoryTestItems))]
|
||||||
|
public static void TestParentDirectory(string path, string expected)
|
||||||
|
{
|
||||||
|
string actual = PathTools.GetParentDirectory(path);
|
||||||
|
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
private static object[][] GetNormalizedPaths(bool getNormalized)
|
private static object[][] GetNormalizedPaths(bool getNormalized)
|
||||||
{
|
{
|
||||||
var normalizedPaths = new HashSet<string>();
|
var normalizedPaths = new HashSet<string>();
|
||||||
|
|
Loading…
Reference in a new issue