Update ConcatenationFileSystem

This commit is contained in:
Alex Barney 2021-07-26 10:18:27 -07:00
parent 8bb6b0e824
commit 2f58e2fd5a
4 changed files with 881 additions and 676 deletions

View File

@ -1,133 +0,0 @@
using System;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{
public class ConcatenationDirectory : IDirectory
{
private OpenDirectoryMode Mode { get; }
private IDirectory ParentDirectory { get; }
private IFileSystem BaseFileSystem { get; }
private ConcatenationFileSystem ParentFileSystem { get; }
private FsPath _path;
public ConcatenationDirectory(ConcatenationFileSystem fs, IFileSystem baseFs, IDirectory parentDirectory, OpenDirectoryMode mode, U8Span path)
{
ParentFileSystem = fs;
BaseFileSystem = baseFs;
ParentDirectory = parentDirectory;
Mode = mode;
StringUtils.Copy(_path.Str, path);
_path.Str[PathTools.MaxPathLength] = StringTraits.NullTerminator;
// Ensure the path ends with a separator
int pathLength = StringUtils.GetLength(path, PathTools.MaxPathLength + 1);
if (pathLength != 0 && _path.Str[pathLength - 1] == StringTraits.DirectorySeparator)
return;
if (pathLength >= PathTools.MaxPathLength)
throw new HorizonResultException(ResultFs.TooLongPath.Value, "abort");
_path.Str[pathLength] = StringTraits.DirectorySeparator;
_path.Str[pathLength + 1] = StringTraits.NullTerminator;
_path.Str[PathTools.MaxPathLength] = StringTraits.NullTerminator;
}
protected override Result DoRead(out long entriesRead, Span<DirectoryEntry> entryBuffer)
{
entriesRead = 0;
var entry = new DirectoryEntry();
Span<DirectoryEntry> entrySpan = SpanHelpers.AsSpan(ref entry);
int i;
for (i = 0; i < entryBuffer.Length; i++)
{
Result rc = ParentDirectory.Read(out long baseEntriesRead, entrySpan);
if (rc.IsFailure()) return rc;
if (baseEntriesRead == 0) break;
// Check if the current open mode says we should return the entry
bool isConcatFile = IsConcatenationFile(entry);
if (!CanReturnEntry(entry, isConcatFile)) continue;
if (isConcatFile)
{
entry.Type = DirectoryEntryType.File;
if (!Mode.HasFlag(OpenDirectoryMode.NoFileSize))
{
string entryName = StringUtils.NullTerminatedUtf8ToString(entry.Name);
string entryFullPath = PathTools.Combine(_path.ToString(), entryName);
rc = ParentFileSystem.GetConcatenationFileSize(out long fileSize, entryFullPath.ToU8Span());
if (rc.IsFailure()) return rc;
entry.Size = fileSize;
}
}
entry.Attributes = NxFileAttributes.None;
entryBuffer[i] = entry;
}
entriesRead = i;
return Result.Success;
}
protected override Result DoGetEntryCount(out long entryCount)
{
entryCount = 0;
long count = 0;
Result rc = BaseFileSystem.OpenDirectory(out IDirectory _, _path,
OpenDirectoryMode.All | OpenDirectoryMode.NoFileSize);
if (rc.IsFailure()) return rc;
var entry = new DirectoryEntry();
Span<DirectoryEntry> entrySpan = SpanHelpers.AsSpan(ref entry);
while (true)
{
rc = ParentDirectory.Read(out long baseEntriesRead, entrySpan);
if (rc.IsFailure()) return rc;
if (baseEntriesRead == 0) break;
if (CanReturnEntry(entry, IsConcatenationFile(entry))) count++;
}
entryCount = count;
return Result.Success;
}
private bool CanReturnEntry(DirectoryEntry entry, bool isConcatFile)
{
return Mode.HasFlag(OpenDirectoryMode.File) && (entry.Type == DirectoryEntryType.File || isConcatFile) ||
Mode.HasFlag(OpenDirectoryMode.Directory) && entry.Type == DirectoryEntryType.Directory && !isConcatFile;
}
private bool IsConcatenationFile(DirectoryEntry entry)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return ConcatenationFileSystem.HasConcatenationFileAttribute(entry.Attributes);
}
else
{
string name = StringUtils.NullTerminatedUtf8ToString(entry.Name);
var fullPath = PathTools.Combine(_path.ToString(), name).ToU8Span();
return ParentFileSystem.IsConcatenationFile(fullPath);
}
}
}
}

View File

@ -1,249 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Util;
namespace LibHac.FsSystem
{
public class ConcatenationFile : IFile
{
private IFileSystem BaseFileSystem { get; }
private U8String FilePath { get; }
private List<IFile> Sources { get; }
private long SubFileSize { get; }
private OpenMode Mode { get; }
internal ConcatenationFile(IFileSystem baseFileSystem, U8Span path, IEnumerable<IFile> sources, long subFileSize, OpenMode mode)
{
BaseFileSystem = baseFileSystem;
FilePath = path.ToU8String();
Sources = sources.ToList();
SubFileSize = subFileSize;
Mode = mode;
for (int i = 0; i < Sources.Count - 1; i++)
{
Sources[i].GetSize(out long actualSubFileSize).ThrowIfFailure();
if (actualSubFileSize != SubFileSize)
{
throw new ArgumentException($"Source file must have size {subFileSize}");
}
}
}
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination,
in ReadOption option)
{
UnsafeHelpers.SkipParamInit(out bytesRead);
long inPos = offset;
int outPos = 0;
Result rc = DryRead(out long remaining, offset, destination.Length, in option, Mode);
if (rc.IsFailure()) return rc;
GetSize(out long fileSize).ThrowIfFailure();
while (remaining > 0)
{
int fileIndex = GetSubFileIndexFromOffset(offset);
IFile file = Sources[fileIndex];
long fileOffset = offset - fileIndex * SubFileSize;
long fileEndOffset = Math.Min((fileIndex + 1) * SubFileSize, fileSize);
int bytesToRead = (int)Math.Min(fileEndOffset - inPos, remaining);
rc = file.Read(out long subFileBytesRead, fileOffset, destination.Slice(outPos, bytesToRead), option);
if (rc.IsFailure()) return rc;
outPos += (int)subFileBytesRead;
inPos += subFileBytesRead;
remaining -= subFileBytesRead;
if (bytesRead < bytesToRead) break;
}
bytesRead = outPos;
return Result.Success;
}
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source, in WriteOption option)
{
Result rc = DryWrite(out _, offset, source.Length, in option, Mode);
if (rc.IsFailure()) return rc;
int inPos = 0;
long outPos = offset;
int remaining = source.Length;
rc = GetSize(out long fileSize);
if (rc.IsFailure()) return rc;
while (remaining > 0)
{
int fileIndex = GetSubFileIndexFromOffset(outPos);
IFile file = Sources[fileIndex];
long fileOffset = outPos - fileIndex * SubFileSize;
long fileEndOffset = Math.Min((fileIndex + 1) * SubFileSize, fileSize);
int bytesToWrite = (int)Math.Min(fileEndOffset - outPos, remaining);
rc = file.Write(fileOffset, source.Slice(inPos, bytesToWrite), option);
if (rc.IsFailure()) return rc;
outPos += bytesToWrite;
inPos += bytesToWrite;
remaining -= bytesToWrite;
}
if (option.HasFlushFlag())
{
return Flush();
}
return Result.Success;
}
protected override Result DoFlush()
{
foreach (IFile file in Sources)
{
Result rc = file.Flush();
if (rc.IsFailure()) return rc;
}
return Result.Success;
}
protected override Result DoGetSize(out long size)
{
UnsafeHelpers.SkipParamInit(out size);
foreach (IFile file in Sources)
{
Result rc = file.GetSize(out long subFileSize);
if (rc.IsFailure()) return rc;
size += subFileSize;
}
return Result.Success;
}
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan<byte> inBuffer)
{
return ResultFs.NotImplemented.Log();
}
protected override Result DoSetSize(long size)
{
Result rc = GetSize(out long currentSize);
if (rc.IsFailure()) return rc;
if (currentSize == size) return Result.Success;
int currentSubFileCount = QuerySubFileCount(currentSize, SubFileSize);
int newSubFileCount = QuerySubFileCount(size, SubFileSize);
if (size > currentSize)
{
IFile currentLastSubFile = Sources[currentSubFileCount - 1];
long newSubFileSize = QuerySubFileSize(currentSubFileCount - 1, size, SubFileSize);
rc = currentLastSubFile.SetSize(newSubFileSize);
if (rc.IsFailure()) return rc;
for (int i = currentSubFileCount; i < newSubFileCount; i++)
{
Unsafe.SkipInit(out FsPath newSubFilePath);
rc = ConcatenationFileSystem.GetSubFilePath(newSubFilePath.Str, FilePath, i);
if (rc.IsFailure()) return rc;
newSubFileSize = QuerySubFileSize(i, size, SubFileSize);
rc = BaseFileSystem.CreateFile(newSubFilePath, newSubFileSize, CreateFileOptions.None);
if (rc.IsFailure()) return rc;
rc = BaseFileSystem.OpenFile(out IFile newSubFile, newSubFilePath, Mode);
if (rc.IsFailure()) return rc;
Sources.Add(newSubFile);
}
}
else
{
for (int i = currentSubFileCount - 1; i > newSubFileCount - 1; i--)
{
Sources[i].Dispose();
Sources.RemoveAt(i);
Unsafe.SkipInit(out FsPath subFilePath);
rc = ConcatenationFileSystem.GetSubFilePath(subFilePath.Str, FilePath, i);
if (rc.IsFailure()) return rc;
rc = BaseFileSystem.DeleteFile(subFilePath);
if (rc.IsFailure()) return rc;
}
long newLastFileSize = QuerySubFileSize(newSubFileCount - 1, size, SubFileSize);
rc = Sources[newSubFileCount - 1].SetSize(newLastFileSize);
if (rc.IsFailure()) return rc;
}
return Result.Success;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
foreach (IFile file in Sources)
{
file?.Dispose();
}
Sources.Clear();
}
}
private int GetSubFileIndexFromOffset(long offset)
{
return (int)(offset / SubFileSize);
}
private static int QuerySubFileCount(long size, long subFileSize)
{
Debug.Assert(size >= 0);
Debug.Assert(subFileSize > 0);
if (size == 0) return 1;
return (int)BitUtil.DivideUp(size, subFileSize);
}
private static long QuerySubFileSize(int subFileIndex, long totalSize, long subFileSize)
{
int subFileCount = QuerySubFileCount(totalSize, subFileSize);
Debug.Assert(subFileIndex < subFileCount);
if (subFileIndex + 1 == subFileCount)
{
long remainder = totalSize % subFileSize;
return remainder == 0 ? subFileSize : remainder;
}
return subFileSize;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LibHac.Tests.FsSystem
{
class ConcatenationFileSystemTests
{
asdf
}
}