mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Update ConcatenationFileSystem
This commit is contained in:
parent
8bb6b0e824
commit
2f58e2fd5a
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
13
tests/LibHac.Tests/FsSystem/ConcatenationFileSystemTests.cs
Normal file
13
tests/LibHac.Tests/FsSystem/ConcatenationFileSystemTests.cs
Normal 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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user