Add ConcatenationFileSystem writing

This commit is contained in:
Alex Barney 2019-01-15 18:37:00 -06:00
parent a33f829b55
commit c3026f04b6
16 changed files with 142 additions and 27 deletions

View file

@ -75,6 +75,13 @@ namespace LibHac.Nand
return Fs.GetAttributes(path); return Fs.GetAttributes(path);
} }
public void SetFileAttributes(string path, FileAttributes attributes)
{
path = ToDiscUtilsPath(PathTools.Normalize(path));
Fs.SetAttributes(path, attributes);
}
public long GetFileSize(string path) public long GetFileSize(string path)
{ {
path = ToDiscUtilsPath(PathTools.Normalize(path)); path = ToDiscUtilsPath(PathTools.Normalize(path));
@ -83,9 +90,9 @@ namespace LibHac.Nand
} }
public void Commit() { } public void Commit() { }
public void CreateDirectory(string path) => throw new NotSupportedException(); public void CreateDirectory(string path) => throw new NotSupportedException();
public void CreateFile(string path, long size) => throw new NotSupportedException(); public void CreateFile(string path, long size, CreateFileOptions options) => throw new NotSupportedException();
public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException(); public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException();
public void RenameFile(string srcPath, string dstPath) => throw new NotSupportedException(); public void RenameFile(string srcPath, string dstPath) => throw new NotSupportedException();

View file

@ -31,7 +31,7 @@ namespace LibHac.IO
BaseFileSystem.CreateDirectory(path); BaseFileSystem.CreateDirectory(path);
} }
public void CreateFile(string path, long size) public void CreateFile(string path, long size, CreateFileOptions options)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View file

@ -33,7 +33,7 @@ namespace LibHac.IO
} }
else else
{ {
long size = ParentFileSystem.GetSplitFileSize(entry.FullPath); long size = ParentFileSystem.GetConcatenationFileSize(entry.FullPath);
yield return new DirectoryEntry(entry.Name, entry.FullPath, DirectoryEntryType.File, size); yield return new DirectoryEntry(entry.Name, entry.FullPath, DirectoryEntryType.File, size);
} }
} }

View file

@ -54,7 +54,26 @@ namespace LibHac.IO
public override void Write(ReadOnlySpan<byte> source, long offset) public override void Write(ReadOnlySpan<byte> source, long offset)
{ {
throw new NotImplementedException(); ValidateWriteParams(source, offset);
long inPos = offset;
int outPos = 0;
int remaining = source.Length;
while (remaining > 0)
{
int fileIndex = GetFileIndexFromOffset(offset);
IFile file = Sources[fileIndex];
long fileOffset = offset - fileIndex * SplitFileSize;
long fileEndOffset = Math.Min((fileIndex + 1) * SplitFileSize, GetSize());
int bytesToWrite = (int)Math.Min(fileEndOffset - inPos, remaining);
file.Write(source.Slice(outPos, bytesToWrite), fileOffset);
outPos += bytesToWrite;
inPos += bytesToWrite;
remaining -= bytesToWrite;
}
} }
public override void Flush() public override void Flush()

View file

@ -6,12 +6,16 @@ namespace LibHac.IO
{ {
public class ConcatenationFileSystem : IFileSystem public class ConcatenationFileSystem : IFileSystem
{ {
private const long DefaultSplitFileSize = 0xFFFF0000; // Hard-coded value used by FS
private IAttributeFileSystem BaseFileSystem { get; } private IAttributeFileSystem BaseFileSystem { get; }
private long SplitFileSize { get; } = 0xFFFF0000; // Hard-coded value used by FS private long SplitFileSize { get; }
public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem) public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem) : this(baseFileSystem, DefaultSplitFileSize) { }
public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem, long splitFileSize)
{ {
BaseFileSystem = baseFileSystem; BaseFileSystem = baseFileSystem;
SplitFileSize = splitFileSize;
} }
internal bool IsSplitFile(string path) internal bool IsSplitFile(string path)
@ -23,22 +27,78 @@ namespace LibHac.IO
public void CreateDirectory(string path) public void CreateDirectory(string path)
{ {
throw new NotImplementedException(); path = PathTools.Normalize(path);
if (FileExists(path))
{
throw new IOException("Cannot create directory because a file with this name already exists.");
}
BaseFileSystem.CreateDirectory(path);
} }
public void CreateFile(string path, long size) public void CreateFile(string path, long size, CreateFileOptions options)
{ {
throw new NotImplementedException(); path = PathTools.Normalize(path);
CreateFileOptions newOptions = options & ~CreateFileOptions.CreateConcatenationFile;
if (!options.HasFlag(CreateFileOptions.CreateConcatenationFile))
{
BaseFileSystem.CreateFile(path, size, newOptions);
return;
}
// A concatenation file directory can't contain normal files
string parentDir = PathTools.GetParentDirectory(path);
if (IsSplitFile(parentDir)) throw new IOException("Cannot create files inside of a concatenation file");
BaseFileSystem.CreateDirectory(path);
FileAttributes attributes = BaseFileSystem.GetFileAttributes(path) | FileAttributes.Archive;
BaseFileSystem.SetFileAttributes(path, attributes);
long remaining = size;
for (int i = 0; remaining > 0; i++)
{
long fileSize = Math.Min(SplitFileSize, remaining);
string fileName = GetSplitFilePath(path, i);
BaseFileSystem.CreateFile(fileName, fileSize, CreateFileOptions.None);
remaining -= fileSize;
}
} }
public void DeleteDirectory(string path) public void DeleteDirectory(string path)
{ {
throw new NotImplementedException(); path = PathTools.Normalize(path);
if (IsSplitFile(path))
{
throw new DirectoryNotFoundException(path);
}
BaseFileSystem.DeleteDirectory(path);
} }
public void DeleteFile(string path) public void DeleteFile(string path)
{ {
throw new NotImplementedException(); path = PathTools.Normalize(path);
if (!IsSplitFile(path))
{
BaseFileSystem.DeleteFile(path);
}
int count = GetSplitFileCount(path);
for (int i = 0; i < count; i++)
{
BaseFileSystem.DeleteFile(GetSplitFilePath(path, i));
}
BaseFileSystem.DeleteDirectory(path);
} }
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode) public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
@ -151,7 +211,7 @@ namespace LibHac.IO
return $"{dirPath}/{index:D2}"; return $"{dirPath}/{index:D2}";
} }
internal long GetSplitFileSize(string path) internal long GetConcatenationFileSize(string path)
{ {
int fileCount = GetSplitFileCount(path); int fileCount = GetSplitFileCount(path);
long size = 0; long size = 0;

View file

@ -11,7 +11,7 @@ namespace LibHac.IO
{ {
public static class FileSystemExtensions public static class FileSystemExtensions
{ {
public static void CopyDirectory(this IDirectory source, IDirectory dest, IProgressReport logger = null) public static void CopyDirectory(this IDirectory source, IDirectory dest, IProgressReport logger = null, CreateFileOptions options = CreateFileOptions.None)
{ {
IFileSystem sourceFs = source.ParentFileSystem; IFileSystem sourceFs = source.ParentFileSystem;
IFileSystem destFs = dest.ParentFileSystem; IFileSystem destFs = dest.ParentFileSystem;
@ -27,12 +27,12 @@ namespace LibHac.IO
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);
subSrcDir.CopyDirectory(subDstDir, logger); subSrcDir.CopyDirectory(subDstDir, logger, options);
} }
if (entry.Type == DirectoryEntryType.File) if (entry.Type == DirectoryEntryType.File)
{ {
destFs.CreateFile(subDstPath, entry.Size); destFs.CreateFile(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)) using (IFile dstFile = destFs.OpenFile(subDstPath, OpenMode.Write))
@ -44,12 +44,12 @@ namespace LibHac.IO
} }
} }
public static void CopyFileSystem(this IFileSystem source, IFileSystem dest, IProgressReport logger = null) public static void CopyFileSystem(this IFileSystem source, IFileSystem dest, IProgressReport logger = null, CreateFileOptions options = CreateFileOptions.None)
{ {
IDirectory sourceRoot = source.OpenDirectory("/", OpenDirectoryMode.All); IDirectory sourceRoot = source.OpenDirectory("/", OpenDirectoryMode.All);
IDirectory destRoot = dest.OpenDirectory("/", OpenDirectoryMode.All); IDirectory destRoot = dest.OpenDirectory("/", OpenDirectoryMode.All);
sourceRoot.CopyDirectory(destRoot, logger); sourceRoot.CopyDirectory(destRoot, logger, options);
} }
public static void Extract(this IFileSystem source, string destinationPath, IProgressReport logger = null) public static void Extract(this IFileSystem source, string destinationPath, IProgressReport logger = null)

View file

@ -5,6 +5,7 @@ namespace LibHac.IO
public interface IAttributeFileSystem : IFileSystem public interface IAttributeFileSystem : IFileSystem
{ {
FileAttributes GetFileAttributes(string path); FileAttributes GetFileAttributes(string path);
void SetFileAttributes(string path, FileAttributes attributes);
long GetFileSize(string path); long GetFileSize(string path);
} }
} }

View file

@ -5,7 +5,7 @@ namespace LibHac.IO
public interface IFileSystem public interface IFileSystem
{ {
void CreateDirectory(string path); void CreateDirectory(string path);
void CreateFile(string path, long size); void CreateFile(string path, long size, CreateFileOptions options);
void DeleteDirectory(string path); void DeleteDirectory(string path);
void DeleteFile(string path); void DeleteFile(string path);
IDirectory OpenDirectory(string path, OpenDirectoryMode mode); IDirectory OpenDirectory(string path, OpenDirectoryMode mode);
@ -25,4 +25,11 @@ namespace LibHac.IO
Files = 2, Files = 2,
All = Directories | Files All = Directories | Files
} }
[Flags]
public enum CreateFileOptions
{
None = 0,
CreateConcatenationFile = 1 << 0
}
} }

View file

@ -100,7 +100,7 @@ namespace LibHac.IO
public void Commit() { } public void Commit() { }
public void CreateDirectory(string path) => throw new NotSupportedException(); public void CreateDirectory(string path) => throw new NotSupportedException();
public void CreateFile(string path, long size) => throw new NotSupportedException(); public void CreateFile(string path, long size, CreateFileOptions options) => throw new NotSupportedException();
public void DeleteDirectory(string path) => throw new NotSupportedException(); public void DeleteDirectory(string path) => throw new NotSupportedException();
public void DeleteFile(string path) => throw new NotSupportedException(); public void DeleteFile(string path) => throw new NotSupportedException();
public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException(); public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException();

View file

@ -23,6 +23,12 @@ namespace LibHac.IO
return File.GetAttributes(ResolveLocalPath(path)); return File.GetAttributes(ResolveLocalPath(path));
} }
public void SetFileAttributes(string path, FileAttributes attributes)
{
path = PathTools.Normalize(path);
File.SetAttributes(ResolveLocalPath(path), attributes);
}
public long GetFileSize(string path) public long GetFileSize(string path)
{ {
path = PathTools.Normalize(path); path = PathTools.Normalize(path);
@ -36,7 +42,7 @@ namespace LibHac.IO
Directory.CreateDirectory(ResolveLocalPath(path)); Directory.CreateDirectory(ResolveLocalPath(path));
} }
public void CreateFile(string path, long size) public void CreateFile(string path, long size, CreateFileOptions options)
{ {
path = PathTools.Normalize(path); path = PathTools.Normalize(path);
string localPath = ResolveLocalPath(path); string localPath = ResolveLocalPath(path);

View file

@ -33,7 +33,7 @@ namespace LibHac.IO
throw new NotSupportedException(); throw new NotSupportedException();
} }
public void CreateFile(string path, long size) public void CreateFile(string path, long size, CreateFileOptions options)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }

View file

@ -76,6 +76,21 @@ namespace LibHac.IO
return sb.ToString(); return sb.ToString();
} }
public static string GetParentDirectory(string path)
{
if (path.Length == 0) return "/";
int i = path.Length - 1;
// A trailing separator should be ignored
if (path[i] == '/') i--;
while (i >= 0 && path[i] != '/') i--;
if (i < 1) return "/";
return path.Substring(0, i);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsDirectorySeparator(char c) internal static bool IsDirectorySeparator(char c)
{ {

View file

@ -148,7 +148,7 @@ namespace LibHac.IO
} }
public void CreateDirectory(string path) => throw new NotSupportedException(); public void CreateDirectory(string path) => throw new NotSupportedException();
public void CreateFile(string path, long size) => throw new NotSupportedException(); public void CreateFile(string path, long size, CreateFileOptions options) => throw new NotSupportedException();
public void DeleteDirectory(string path) => throw new NotSupportedException(); public void DeleteDirectory(string path) => throw new NotSupportedException();
public void DeleteFile(string path) => throw new NotSupportedException(); public void DeleteFile(string path) => throw new NotSupportedException();
public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException(); public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException();

View file

@ -122,7 +122,7 @@ namespace LibHac.IO.Save
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public void CreateFile(string path, long size) public void CreateFile(string path, long size, CreateFileOptions options)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }

View file

@ -66,7 +66,7 @@ namespace LibHac.IO.Save
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public void CreateFile(string path, long size) public void CreateFile(string path, long size, CreateFileOptions options)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }

View file

@ -24,11 +24,11 @@
ParentFileSystem.CreateDirectory(ResolveFullPath(path)); ParentFileSystem.CreateDirectory(ResolveFullPath(path));
} }
public void CreateFile(string path, long size) public void CreateFile(string path, long size, CreateFileOptions options)
{ {
path = PathTools.Normalize(path); path = PathTools.Normalize(path);
ParentFileSystem.CreateFile(ResolveFullPath(path), size); ParentFileSystem.CreateFile(ResolveFullPath(path), size, options);
} }
public void DeleteDirectory(string path) public void DeleteDirectory(string path)