Add ConcatenationFileSystem

This commit is contained in:
Alex Barney 2019-01-06 23:24:58 -06:00
parent 404e05da53
commit cf48b5b21e
3 changed files with 308 additions and 0 deletions

View file

@ -0,0 +1,62 @@
using System.Collections.Generic;
namespace LibHac.IO
{
public class ConcatenationDirectory : IDirectory
{
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
public string FullPath { get; }
public OpenDirectoryMode Mode { get; }
private ConcatenationFileSystem ParentFileSystem { get; }
private IDirectory ParentDirectory { get; }
public ConcatenationDirectory(ConcatenationFileSystem fs, IDirectory parentDirectory, OpenDirectoryMode mode)
{
ParentFileSystem = fs;
ParentDirectory = parentDirectory;
Mode = mode;
FullPath = parentDirectory.FullPath;
}
public IEnumerable<DirectoryEntry> Read()
{
foreach (DirectoryEntry entry in ParentDirectory.Read())
{
bool isSplit = ParentFileSystem.IsSplitFile(entry.FullPath);
if (!CanReturnEntry(entry, isSplit)) continue;
if (!isSplit)
{
yield return entry;
}
else
{
long size = ParentFileSystem.GetSplitFileSize(entry.FullPath);
yield return new DirectoryEntry(entry.Name, entry.FullPath, DirectoryEntryType.File, size);
}
}
}
public int GetEntryCount()
{
int count = 0;
foreach (DirectoryEntry entry in ParentDirectory.Read())
{
bool isSplit = ParentFileSystem.IsSplitFile(entry.FullPath);
if (CanReturnEntry(entry, isSplit)) count++;
}
return count;
}
private bool CanReturnEntry(DirectoryEntry entry, bool isSplit)
{
return Mode.HasFlag(OpenDirectoryMode.Files) && (entry.Type == DirectoryEntryType.File || isSplit) ||
Mode.HasFlag(OpenDirectoryMode.Directories) && entry.Type == DirectoryEntryType.Directory && !isSplit;
}
}
}

View file

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace LibHac.IO
{
public class ConcatenationFile : FileBase
{
private IFile[] Sources { get; }
private long SplitFileSize { get; }
public ConcatenationFile(IList<IFile> sources, long splitFileSize, OpenMode mode)
{
Sources = sources.ToArray();
SplitFileSize = splitFileSize;
Mode = mode;
for (int i = 0; i < Sources.Length - 1; i++)
{
if (Sources[i].GetSize() != SplitFileSize)
{
throw new ArgumentException($"Source file must have size {splitFileSize}");
}
}
}
public override int Read(Span<byte> destination, long offset)
{
long inPos = offset;
int outPos = 0;
int remaining = ValidateReadParamsAndGetSize(destination, offset);
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 bytesToRead = (int)Math.Min(fileEndOffset - inPos, remaining);
int bytesRead = file.Read(destination.Slice(outPos, bytesToRead), fileOffset);
outPos += bytesRead;
inPos += bytesRead;
remaining -= bytesRead;
if (bytesRead < bytesToRead) break;
}
return outPos;
}
public override void Write(ReadOnlySpan<byte> source, long offset)
{
throw new NotImplementedException();
}
public override void Flush()
{
foreach (IFile file in Sources)
{
file.Flush();
}
}
public override long GetSize()
{
long size = 0;
foreach (IFile file in Sources)
{
size += file.GetSize();
}
return size;
}
public override void SetSize(long size)
{
throw new NotImplementedException();
}
private int GetFileIndexFromOffset(long offset)
{
return (int)(offset / SplitFileSize);
}
}
}

View file

@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace LibHac.IO
{
public class ConcatenationFileSystem : IFileSystem
{
private IAttributeFileSystem BaseFileSystem { get; }
private long SplitFileSize { get; } = 0xFFFF0000; // Hard-coded value used by FS
public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem)
{
BaseFileSystem = baseFileSystem;
}
internal bool IsSplitFile(string path)
{
FileAttributes attributes = BaseFileSystem.GetFileAttributes(path);
return (attributes & FileAttributes.Directory) != 0 && (attributes & FileAttributes.Archive) != 0;
}
public void CreateDirectory(string path)
{
throw new NotImplementedException();
}
public void CreateFile(string path, long size)
{
throw new NotImplementedException();
}
public void DeleteDirectory(string path)
{
throw new NotImplementedException();
}
public void DeleteFile(string path)
{
throw new NotImplementedException();
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
{
path = PathTools.Normalize(path);
if (IsSplitFile(path))
{
throw new DirectoryNotFoundException(path);
}
IDirectory parentDir = BaseFileSystem.OpenDirectory(path, OpenDirectoryMode.All);
var dir = new ConcatenationDirectory(this, parentDir, mode);
return dir;
}
public IFile OpenFile(string path, OpenMode mode)
{
path = PathTools.Normalize(path);
if (!IsSplitFile(path))
{
return BaseFileSystem.OpenFile(path, mode);
}
int fileCount = GetSplitFileCount(path);
var files = new List<IFile>();
for (int i = 0; i < fileCount; i++)
{
string filePath = GetSplitFilePath(path, i);
IFile file = BaseFileSystem.OpenFile(filePath, mode);
files.Add(file);
}
return new ConcatenationFile(files, SplitFileSize, mode);
}
public void RenameDirectory(string srcPath, string dstPath)
{
srcPath = PathTools.Normalize(srcPath);
dstPath = PathTools.Normalize(dstPath);
if (IsSplitFile(srcPath))
{
throw new DirectoryNotFoundException();
}
BaseFileSystem.RenameDirectory(srcPath, dstPath);
}
public void RenameFile(string srcPath, string dstPath)
{
srcPath = PathTools.Normalize(srcPath);
dstPath = PathTools.Normalize(dstPath);
if (IsSplitFile(srcPath))
{
BaseFileSystem.RenameDirectory(srcPath, dstPath);
}
else
{
BaseFileSystem.RenameFile(srcPath, dstPath);
}
}
public bool DirectoryExists(string path)
{
path = PathTools.Normalize(path);
return BaseFileSystem.DirectoryExists(path) && !IsSplitFile(path);
}
public bool FileExists(string path)
{
path = PathTools.Normalize(path);
return BaseFileSystem.FileExists(path) || BaseFileSystem.DirectoryExists(path) && IsSplitFile(path);
}
public void Commit()
{
BaseFileSystem.Commit();
}
private int GetSplitFileCount(string dirPath)
{
int count = 0;
while (BaseFileSystem.FileExists(GetSplitFilePath(dirPath, count)))
{
count++;
}
return count;
}
private static string GetSplitFilePath(string dirPath, int index)
{
return $"{dirPath}/{index:D2}";
}
internal long GetSplitFileSize(string path)
{
int fileCount = GetSplitFileCount(path);
long size = 0;
for (int i = 0; i < fileCount; i++)
{
size += BaseFileSystem.GetFileSize(GetSplitFilePath(path, i));
}
return size;
}
}
}