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
|
||||
increment: Patch
|
||||
next-version: 0.5.0
|
||||
branches:
|
||||
master:
|
||||
tag: alpha
|
|
@ -19,7 +19,7 @@ namespace LibHac.Fs
|
|||
if (!BaseFs.DirectoryExists(WorkingDir))
|
||||
{
|
||||
BaseFs.CreateDirectory(WorkingDir);
|
||||
BaseFs.CreateDirectory(CommittedDir);
|
||||
BaseFs.EnsureDirectoryExists(CommittedDir);
|
||||
}
|
||||
|
||||
if (BaseFs.DirectoryExists(CommittedDir))
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace LibHac.Fs
|
|||
{
|
||||
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 (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 ((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 (offset < 0) ThrowHelper.ThrowResult(ResultFs.ValueOutOfRange, "Offset must be non-negative.");
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace LibHac.Fs
|
|||
|
||||
if (entry.Type == DirectoryEntryType.Directory)
|
||||
{
|
||||
destFs.CreateDirectory(subDstPath);
|
||||
destFs.EnsureDirectoryExists(subDstPath);
|
||||
IDirectory subSrcDir = sourceFs.OpenDirectory(subSrcPath, OpenDirectoryMode.All);
|
||||
IDirectory subDstDir = destFs.OpenDirectory(subDstPath, OpenDirectoryMode.All);
|
||||
|
||||
|
@ -28,7 +28,7 @@ namespace LibHac.Fs
|
|||
|
||||
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 dstFile = destFs.OpenFile(subDstPath, OpenMode.Write | OpenMode.Append))
|
||||
|
@ -209,6 +209,54 @@ namespace LibHac.Fs
|
|||
{
|
||||
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]
|
||||
|
|
|
@ -509,7 +509,7 @@ namespace LibHac.Fs
|
|||
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 maxMountLen = Math.Min(path.Length, PathTools.MountNameLength);
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace LibHac.Fs
|
|||
|
||||
if (entry.Type == DirectoryEntryType.Directory)
|
||||
{
|
||||
fs.CreateDirectory(subDstPath);
|
||||
fs.EnsureDirectoryExists(subDstPath);
|
||||
|
||||
fs.CopyDirectory(subSrcPath, subDstPath, options, logger);
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ namespace LibHac.Fs
|
|||
if (entry.Type == DirectoryEntryType.File)
|
||||
{
|
||||
logger?.LogMessage(subSrcPath);
|
||||
fs.CreateFile(subDstPath, entry.Size, options);
|
||||
fs.CreateOrOverwriteFile(subDstPath, entry.Size, options);
|
||||
|
||||
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.IO;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
|
@ -12,7 +11,13 @@ namespace LibHac.Fs
|
|||
/// Creates all directories and subdirectories in the specified path unless they already exist.
|
||||
/// </summary>
|
||||
/// <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);
|
||||
|
||||
/// <summary>
|
||||
|
@ -22,39 +27,58 @@ namespace LibHac.Fs
|
|||
/// <param name="size">The initial size of the created file.</param>
|
||||
/// <param name="options">Flags to control how the file is created.
|
||||
/// 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);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the specified directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the directory to delete.</param>
|
||||
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
|
||||
/// <exception cref="IOException">An I/O error occurred while deleting the directory.</exception>
|
||||
/// <remarks>
|
||||
/// 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);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the specified directory and any subdirectories and files in the directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the directory to delete.</param>
|
||||
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
|
||||
/// <exception cref="IOException">An I/O error occurred while deleting the directory.</exception>
|
||||
/// <remarks>
|
||||
/// 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);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes any subdirectories and files in the specified directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the directory to clean.</param>
|
||||
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
|
||||
/// <exception cref="IOException">An I/O error occurred while deleting the directory.</exception>
|
||||
/// <remarks>
|
||||
/// 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);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the specified file.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the file to delete.</param>
|
||||
/// <exception cref="FileNotFoundException">The specified file does not exist.</exception>
|
||||
/// <exception cref="IOException">An I/O error occurred while deleting the file.</exception>
|
||||
/// <remarks>
|
||||
/// 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);
|
||||
|
||||
/// <summary>
|
||||
|
@ -63,8 +87,11 @@ namespace LibHac.Fs
|
|||
/// <param name="path">The directory's full path.</param>
|
||||
/// <param name="mode">Specifies which sub-entries should be enumerated.</param>
|
||||
/// <returns>An <see cref="IDirectory"/> instance for the specified directory.</returns>
|
||||
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
|
||||
/// <exception cref="IOException">An I/O error occurred while opening the directory.</exception>
|
||||
/// <remarks>
|
||||
/// 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);
|
||||
|
||||
/// <summary>
|
||||
|
@ -73,8 +100,11 @@ namespace LibHac.Fs
|
|||
/// <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>
|
||||
/// <returns>An <see cref="IFile"/> instance for the specified path.</returns>
|
||||
/// <exception cref="FileNotFoundException">The specified file does not exist.</exception>
|
||||
/// <exception cref="IOException">An I/O error occurred while deleting the file.</exception>
|
||||
/// <remarks>
|
||||
/// 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);
|
||||
|
||||
/// <summary>
|
||||
|
@ -82,8 +112,16 @@ namespace LibHac.Fs
|
|||
/// </summary>
|
||||
/// <param name="srcPath">The full path of the directory to rename.</param>
|
||||
/// <param name="dstPath">The new full path of the directory.</param>
|
||||
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
|
||||
/// <exception cref="IOException">An I/O error occurred while deleting the directory.</exception>
|
||||
/// <returns>An <see cref="IFile"/> instance for the specified path.</returns>
|
||||
/// <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);
|
||||
|
||||
/// <summary>
|
||||
|
@ -91,7 +129,14 @@ namespace LibHac.Fs
|
|||
/// </summary>
|
||||
/// <param name="srcPath">The full path of the file to rename.</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);
|
||||
|
||||
/// <summary>
|
||||
|
@ -99,6 +144,11 @@ namespace LibHac.Fs
|
|||
/// </summary>
|
||||
/// <param name="path">The full path to check.</param>
|
||||
/// <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);
|
||||
|
||||
/// <summary>
|
||||
|
@ -121,6 +171,11 @@ namespace LibHac.Fs
|
|||
/// <param name="path">The path of the file or directory.</param>
|
||||
/// <returns>The timestamps for the specified file or directory.
|
||||
/// 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);
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
|
@ -20,7 +21,21 @@ namespace LibHac.Fs
|
|||
LocalPath = fs.ResolveLocalPath(path);
|
||||
Mode = mode;
|
||||
|
||||
DirInfo = new DirectoryInfo(LocalPath);
|
||||
try
|
||||
{
|
||||
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()
|
||||
|
|
|
@ -5,15 +5,16 @@ namespace LibHac.Fs
|
|||
{
|
||||
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 StreamFile File { get; }
|
||||
|
||||
public LocalFile(string path, OpenMode mode)
|
||||
{
|
||||
Path = path;
|
||||
Mode = mode;
|
||||
Stream = new FileStream(Path, FileMode.Open, GetFileAccess(mode), GetFileShare(mode));
|
||||
Stream = OpenFile(path, mode);
|
||||
File = new StreamFile(Stream, mode);
|
||||
|
||||
ToDispose.Add(File);
|
||||
|
@ -48,7 +49,15 @@ namespace LibHac.Fs
|
|||
|
||||
public override void SetSize(long size)
|
||||
{
|
||||
File.SetSize(size);
|
||||
try
|
||||
{
|
||||
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)
|
||||
|
@ -61,5 +70,25 @@ namespace LibHac.Fs
|
|||
{
|
||||
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.IO;
|
||||
using System.Security;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
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; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -46,81 +52,116 @@ namespace LibHac.Fs
|
|||
|
||||
public long GetFileSize(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
var info = new FileInfo(ResolveLocalPath(path));
|
||||
return info.Length;
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
FileInfo info = GetFileInfo(localPath);
|
||||
return GetSizeInternal(info);
|
||||
}
|
||||
|
||||
public void CreateDirectory(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
Directory.CreateDirectory(ResolveLocalPath(path));
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(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)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
string localPath = ResolveLocalPath(path);
|
||||
string localDir = ResolveLocalPath(PathTools.GetParentDirectory(path));
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
string localPath = ResolveLocalPath(path);
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
foreach (string file in Directory.EnumerateFiles(localPath))
|
||||
{
|
||||
File.Delete(file);
|
||||
DeleteFileInternal(GetFileInfo(file));
|
||||
}
|
||||
|
||||
foreach (string dir in Directory.EnumerateDirectories(localPath))
|
||||
{
|
||||
Directory.Delete(dir, true);
|
||||
DeleteDirectoryInternal(GetDirInfo(dir), true);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
string resolveLocalPath = ResolveLocalPath(path);
|
||||
File.Delete(resolveLocalPath);
|
||||
FileInfo file = GetFileInfo(localPath);
|
||||
|
||||
DeleteFileInternal(file);
|
||||
}
|
||||
|
||||
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
if (GetEntryType(path) == DirectoryEntryType.File)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
}
|
||||
|
||||
return new LocalDirectory(this, path, 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);
|
||||
}
|
||||
|
||||
|
@ -129,38 +170,49 @@ namespace LibHac.Fs
|
|||
srcPath = PathTools.Normalize(srcPath);
|
||||
dstPath = PathTools.Normalize(dstPath);
|
||||
|
||||
string srcLocalPath = ResolveLocalPath(srcPath);
|
||||
string dstLocalPath = ResolveLocalPath(dstPath);
|
||||
// Official FS behavior is to do nothing in this case
|
||||
if (srcPath == dstPath) return;
|
||||
|
||||
string directoryName = ResolveLocalPath(PathTools.GetParentDirectory(dstPath));
|
||||
if (directoryName != null) Directory.CreateDirectory(directoryName);
|
||||
Directory.Move(srcLocalPath, dstLocalPath);
|
||||
// FS does the subpath check before verifying the path exists
|
||||
if (PathTools.IsSubPath(srcPath.AsSpan(), dstPath.AsSpan()))
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.DestinationIsSubPathOfSource);
|
||||
}
|
||||
|
||||
DirectoryInfo srcDir = GetDirInfo(ResolveLocalPath(srcPath));
|
||||
DirectoryInfo dstDir = GetDirInfo(ResolveLocalPath(dstPath));
|
||||
|
||||
RenameDirInternal(srcDir, dstDir);
|
||||
}
|
||||
|
||||
public void RenameFile(string srcPath, string dstPath)
|
||||
{
|
||||
srcPath = PathTools.Normalize(srcPath);
|
||||
dstPath = PathTools.Normalize(dstPath);
|
||||
string srcLocalPath = ResolveLocalPath(PathTools.Normalize(srcPath));
|
||||
string dstLocalPath = ResolveLocalPath(PathTools.Normalize(dstPath));
|
||||
|
||||
string srcLocalPath = ResolveLocalPath(srcPath);
|
||||
string dstLocalPath = ResolveLocalPath(dstPath);
|
||||
string dstLocalDir = ResolveLocalPath(PathTools.GetParentDirectory(dstPath));
|
||||
// Official FS behavior is to do nothing in this case
|
||||
if (srcLocalPath == dstLocalPath) return;
|
||||
|
||||
if (dstLocalDir != null) Directory.CreateDirectory(dstLocalDir);
|
||||
File.Move(srcLocalPath, dstLocalPath);
|
||||
FileInfo srcFile = GetFileInfo(srcLocalPath);
|
||||
FileInfo dstFile = GetFileInfo(dstLocalPath);
|
||||
|
||||
RenameFileInternal(srcFile, dstFile);
|
||||
}
|
||||
|
||||
public DirectoryEntryType GetEntryType(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
string localPath = ResolveLocalPath(path);
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
if (Directory.Exists(localPath))
|
||||
DirectoryInfo dir = GetDirInfo(localPath);
|
||||
|
||||
if (dir.Exists)
|
||||
{
|
||||
return DirectoryEntryType.Directory;
|
||||
}
|
||||
|
||||
if (File.Exists(localPath))
|
||||
FileInfo file = GetFileInfo(localPath);
|
||||
|
||||
if (file.Exists)
|
||||
{
|
||||
return DirectoryEntryType.File;
|
||||
}
|
||||
|
@ -170,8 +222,9 @@ namespace LibHac.Fs
|
|||
|
||||
public FileTimeStampRaw GetFileTimeStampRaw(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
string localPath = ResolveLocalPath(path);
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
if (!GetFileInfo(localPath).Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
|
||||
FileTimeStampRaw timeStamp = default;
|
||||
|
||||
|
@ -196,5 +249,232 @@ namespace LibHac.Fs
|
|||
|
||||
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) =>
|
||||
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)
|
||||
{
|
||||
if (path.Length == 0) return "/";
|
||||
Debug.Assert(IsNormalized(path.AsSpan()));
|
||||
|
||||
int i = path.Length - 1;
|
||||
|
||||
// Handles non-mounted root paths
|
||||
if (i == 0) return string.Empty;
|
||||
|
||||
// A trailing separator should be ignored
|
||||
if (path[i] == '/') i--;
|
||||
|
||||
// Handles mounted root paths
|
||||
if (i >= 0 && path[i] == ':') return string.Empty;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -296,50 +304,74 @@ namespace LibHac.Fs
|
|||
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(path));
|
||||
Debug.Assert(IsNormalized(path1));
|
||||
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.
|
||||
// Both are treated the same.
|
||||
int rootLength = rootPath[rootPath.Length - 1] == DirectorySeparator
|
||||
? rootPath.Length - 1
|
||||
: rootPath.Length;
|
||||
if (path2[path2.Length - 1] == DirectorySeparator)
|
||||
{
|
||||
path2 = path2.Slice(0, path2.Length - 1);
|
||||
}
|
||||
|
||||
// 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;
|
||||
ReadOnlySpan<char> shortPath = path1.Length < path2.Length ? path1 : path2;
|
||||
ReadOnlySpan<char> 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 bool IsSubPath(ReadOnlySpan<byte> rootPath, ReadOnlySpan<byte> 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<byte> path1, ReadOnlySpan<byte> path2)
|
||||
{
|
||||
Debug.Assert(IsNormalized(rootPath));
|
||||
Debug.Assert(IsNormalized(path));
|
||||
Debug.Assert(IsNormalized(path1));
|
||||
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.
|
||||
// Both are treated the same.
|
||||
int rootLength = rootPath[rootPath.Length - 1] == DirectorySeparator
|
||||
? rootPath.Length - 1
|
||||
: rootPath.Length;
|
||||
if (path2[path2.Length - 1] == DirectorySeparator)
|
||||
{
|
||||
path2 = path2.Slice(0, path2.Length - 1);
|
||||
}
|
||||
|
||||
// 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;
|
||||
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)
|
||||
|
@ -366,6 +398,20 @@ namespace LibHac.Fs
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -373,12 +419,12 @@ namespace LibHac.Fs
|
|||
{
|
||||
if (path[i] == MountSeparator)
|
||||
{
|
||||
mountName = path.Substring(0, i);
|
||||
length = i;
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
mountName = default;
|
||||
length = default;
|
||||
return ResultFs.InvalidMountName;
|
||||
}
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
public static Result DirectoryUnobtainable => new Result(ModuleFs, 6006);
|
||||
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 DifferentDestFileSystem => new Result(ModuleFs, 6034);
|
||||
public static Result InvalidOffset => new Result(ModuleFs, 6061);
|
||||
|
@ -79,6 +80,8 @@
|
|||
|
||||
public static Result InvalidOpenModeOperation => new Result(ModuleFs, 6200);
|
||||
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 UnsupportedOperationInMemoryStorageSetSize => new Result(ModuleFs, 6316);
|
||||
|
|
|
@ -344,7 +344,7 @@ namespace LibHac.Fs.Save
|
|||
{
|
||||
throw new IOException(Messages.DestPathAlreadyExists);
|
||||
}
|
||||
|
||||
|
||||
ReadOnlySpan<byte> oldPathBytes = Util.GetUtf8Bytes(srcPath);
|
||||
ReadOnlySpan<byte> newPathBytes = Util.GetUtf8Bytes(dstPath);
|
||||
|
||||
|
@ -362,7 +362,7 @@ namespace LibHac.Fs.Save
|
|||
|
||||
if (PathTools.IsSubPath(oldPathBytes, newPathBytes))
|
||||
{
|
||||
throw new IOException(Messages.DestPathIsSubPath);
|
||||
ThrowHelper.ThrowResult(ResultFs.DestinationIsSubPathOfSource);
|
||||
}
|
||||
|
||||
if (oldKey.Parent != newKey.Parent)
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<RepositoryType>git</RepositoryType>
|
||||
<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>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
namespace LibHac
|
||||
using System;
|
||||
|
||||
namespace LibHac
|
||||
{
|
||||
internal static class ThrowHelper
|
||||
{
|
||||
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)
|
||||
{
|
||||
fs.CreateDirectory(subDstPath);
|
||||
fs.EnsureDirectoryExists(subDstPath);
|
||||
|
||||
CopyDirectoryWithProgressInternal(fs, subSrcPath, subDstPath, options, logger);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ namespace hactoolnet
|
|||
if (entry.Type == DirectoryEntryType.File)
|
||||
{
|
||||
logger?.LogMessage(subSrcPath);
|
||||
fs.CreateFile(subDstPath, entry.Size, options);
|
||||
fs.CreateOrOverwriteFile(subDstPath, entry.Size, options);
|
||||
|
||||
CopyFileWithProgress(fs, subSrcPath, subDstPath, logger);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<VersionPrefix>0.4.1</VersionPrefix>
|
||||
<VersionPrefix>0.5.0</VersionPrefix>
|
||||
<PathMap Condition=" '$(BuildType)' == 'Release' ">$(MSBuildProjectDirectory)=C:/hactoolnet/</PathMap>
|
||||
</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/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);
|
||||
|
@ -107,6 +133,24 @@ namespace LibHac.Tests
|
|||
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)
|
||||
{
|
||||
var normalizedPaths = new HashSet<string>();
|
||||
|
|
Loading…
Reference in a new issue