Add LocalFileSystem

This commit is contained in:
Alex Barney 2018-12-31 19:15:35 -07:00
parent 484540c4b2
commit efcc7d04c7
13 changed files with 377 additions and 68 deletions

View file

@ -22,6 +22,7 @@ namespace LibHac.IO
for (int i = 0; i < cacheSize; i++) for (int i = 0; i < cacheSize; i++)
{ {
// todo why is this rented?
var block = new CacheBlock { Buffer = ArrayPool<byte>.Shared.Rent(blockSize) }; var block = new CacheBlock { Buffer = ArrayPool<byte>.Shared.Rent(blockSize) };
Blocks.AddLast(block); Blocks.AddLast(block);
} }

View file

@ -3,12 +3,14 @@
public class DirectoryEntry public class DirectoryEntry
{ {
public string Name { get; } public string Name { get; }
public string FullPath { get; }
public DirectoryEntryType Type { get; } public DirectoryEntryType Type { get; }
public long Size { get; } public long Size { get; }
public DirectoryEntry(string name, DirectoryEntryType type, long size) public DirectoryEntry(string name, string fullPath, DirectoryEntryType type, long size)
{ {
Name = name; Name = name;
FullPath = PathTools.Normalize(fullPath);
Type = type; Type = type;
Size = size; Size = size;
} }

View file

@ -4,20 +4,80 @@ namespace LibHac.IO
{ {
public abstract class FileBase : IFile public abstract class FileBase : IFile
{ {
private bool _isDisposed;
public abstract int Read(Span<byte> destination, long offset); public abstract int Read(Span<byte> destination, long offset);
public abstract void Write(ReadOnlySpan<byte> source, long offset); public abstract void Write(ReadOnlySpan<byte> source, long offset);
public abstract void Flush(); public abstract void Flush();
public abstract long GetSize(); public abstract long GetSize();
public abstract long SetSize(); public abstract void SetSize(long size);
protected int GetAvailableSizeAndValidate(ReadOnlySpan<byte> span, long offset) protected OpenMode Mode { get; set; }
protected int ValidateReadParamsAndGetSize(ReadOnlySpan<byte> span, long offset)
{ {
long fileLength = GetSize(); if (_isDisposed) throw new ObjectDisposedException(null);
if ((Mode & OpenMode.Read) == 0) throw new NotSupportedException("File does not allow reading.");
if (span == null) throw new ArgumentNullException(nameof(span));
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "Argument must be non-negative.");
long fileSize = GetSize();
int size = span.Length;
if (offset > fileSize) throw new ArgumentOutOfRangeException(nameof(offset), "Offset must be less than the file size.");
return (int)Math.Min(fileSize - offset, size);
}
protected void ValidateWriteParams(ReadOnlySpan<byte> span, long offset)
{
if (_isDisposed) throw new ObjectDisposedException(null);
if ((Mode & OpenMode.Write) == 0) throw new NotSupportedException("File does not allow writing.");
if (span == null) throw new ArgumentNullException(nameof(span)); if (span == null) throw new ArgumentNullException(nameof(span));
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "Argument must be non-negative."); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "Argument must be non-negative.");
return (int)Math.Min(fileLength - offset, span.Length); long fileSize = GetSize();
int size = span.Length;
if (offset + size > fileSize)
{
if ((Mode & OpenMode.Append) == 0)
{
throw new NotSupportedException("File does not allow appending.");
}
SetSize(offset + size);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_isDisposed) return;
if (disposing)
{
Flush();
}
_isDisposed = true;
} }
} }
[Flags]
public enum OpenMode
{
Read = 1,
Write = 2,
Append = 4,
ReadWrite = Read | Write
}
} }

View file

@ -1,68 +1,80 @@
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
namespace LibHac.IO namespace LibHac.IO
{ {
public static class FileSystemExtensions public static class FileSystemExtensions
{ {
public static void Extract(this IFileSystem fs, string outDir) // todo add progress logging
public static void CopyDirectory(this IDirectory source, IDirectory dest)
{ {
IDirectory root = fs.OpenDirectory("/", OpenDirectoryMode.All); IFileSystem sourceFs = source.ParentFileSystem;
IFileSystem destFs = dest.ParentFileSystem;
foreach (string filename in root.EnumerateFiles()) foreach (DirectoryEntry entry in source.Read())
{ {
//Console.WriteLine(filename); string subSrcPath = source.FullPath + '/' + entry.Name;
IFile file = fs.OpenFile(filename); string subDstPath = dest.FullPath + '/' + entry.Name;
string outPath = Path.Combine(outDir, filename.TrimStart('/'));
string directoryName = Path.GetDirectoryName(outPath);
if(!string.IsNullOrWhiteSpace(directoryName)) Directory.CreateDirectory(directoryName);
using (var outFile = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite))
{
file.CopyTo(outFile);
}
}
}
public static IEnumerable<string> EnumerateFiles(this IDirectory directory)
{
DirectoryEntry[] entries = directory.Read();
foreach (DirectoryEntry entry in entries)
{
if (entry.Type == DirectoryEntryType.Directory) if (entry.Type == DirectoryEntryType.Directory)
{ {
foreach (string a in EnumerateFiles(directory.ParentFileSystem.OpenDirectory(entry.Name, OpenDirectoryMode.All))) destFs.CreateDirectory(subDstPath);
{ IDirectory subSrcDir = sourceFs.OpenDirectory(subSrcPath, OpenDirectoryMode.All);
yield return a; IDirectory subDstDir = destFs.OpenDirectory(subDstPath, OpenDirectoryMode.All);
}
subSrcDir.CopyDirectory(subDstDir);
} }
if (entry.Type == DirectoryEntryType.File) if (entry.Type == DirectoryEntryType.File)
{ {
yield return entry.Name; destFs.CreateFile(subDstPath, entry.Size);
using (IFile srcFile = sourceFs.OpenFile(subSrcPath, OpenMode.Read))
using (IFile dstFile = destFs.OpenFile(subDstPath, OpenMode.Write))
{
srcFile.CopyTo(dstFile);
}
} }
} }
} }
public static void CopyTo(this IFile file, Stream output) public static IEnumerable<DirectoryEntry> EnumerateEntries(this IDirectory directory)
{
IFileSystem fs = directory.ParentFileSystem;
foreach (DirectoryEntry entry in directory.Read())
{
yield return entry;
if (entry.Type != DirectoryEntryType.Directory) continue;
IDirectory subDir = fs.OpenDirectory(directory.FullPath + '/' + entry.Name, OpenDirectoryMode.All);
foreach (DirectoryEntry subEntry in subDir.EnumerateEntries())
{
yield return subEntry;
}
}
}
// todo add progress logging
public static void CopyTo(this IFile file, IFile dest)
{ {
const int bufferSize = 0x8000; const int bufferSize = 0x8000;
long remaining = file.GetSize();
long inOffset = 0;
var buffer = new byte[bufferSize];
while (remaining > 0) byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try
{ {
int toWrite = (int)Math.Min(buffer.Length, remaining); long inOffset = 0;
file.Read(buffer.AsSpan(0, toWrite), inOffset);
output.Write(buffer, 0, toWrite); int bytesRead;
remaining -= toWrite; while ((bytesRead = file.Read(buffer, inOffset)) != 0)
inOffset += toWrite; {
dest.Write(buffer.AsSpan(0, bytesRead), inOffset);
inOffset += bytesRead;
}
} }
finally { ArrayPool<byte>.Shared.Return(buffer); }
} }
} }
} }

View file

@ -1,10 +1,13 @@
namespace LibHac.IO using System.Collections.Generic;
namespace LibHac.IO
{ {
public interface IDirectory public interface IDirectory
{ {
IFileSystem ParentFileSystem { get; } IFileSystem ParentFileSystem { get; }
string FullPath { get; }
DirectoryEntry[] Read(); IEnumerable<DirectoryEntry> Read();
int GetEntryCount(); int GetEntryCount();
} }
} }

View file

@ -2,12 +2,12 @@
namespace LibHac.IO namespace LibHac.IO
{ {
public interface IFile public interface IFile : IDisposable
{ {
int Read(Span<byte> destination, long offset); int Read(Span<byte> destination, long offset);
void Write(ReadOnlySpan<byte> source, long offset); void Write(ReadOnlySpan<byte> source, long offset);
void Flush(); void Flush();
long GetSize(); long GetSize();
long SetSize(); void SetSize(long size);
} }
} }

View file

@ -4,17 +4,17 @@ namespace LibHac.IO
{ {
public interface IFileSystem public interface IFileSystem
{ {
void Commit();
void CreateDirectory(string path); void CreateDirectory(string path);
void CreateFile(string path, long size); void CreateFile(string path, long size);
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);
IFile OpenFile(string path); IFile OpenFile(string path, OpenMode mode);
void RenameDirectory(string srcPath, string dstPath); void RenameDirectory(string srcPath, string dstPath);
void RenameFile(string srcPath, string dstPath); void RenameFile(string srcPath, string dstPath);
bool DirectoryExists(string path); bool DirectoryExists(string path);
bool FileExists(string path); bool FileExists(string path);
void Commit();
} }
[Flags] [Flags]

View file

@ -0,0 +1,66 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace LibHac.IO
{
public class LocalDirectory : IDirectory
{
public IFileSystem ParentFileSystem { get; }
public string FullPath { get; }
private string LocalPath { get; }
private OpenDirectoryMode Mode { get; }
private DirectoryInfo DirInfo { get; }
public LocalDirectory(LocalFileSystem fs, string path, OpenDirectoryMode mode)
{
ParentFileSystem = fs;
FullPath = path;
LocalPath = fs.ResolveLocalPath(path);
Mode = mode;
DirInfo = new DirectoryInfo(LocalPath);
}
public IEnumerable<DirectoryEntry> Read()
{
var entries = new List<DirectoryEntry>();
if (Mode.HasFlag(OpenDirectoryMode.Directories))
{
foreach (DirectoryInfo dir in DirInfo.EnumerateDirectories())
{
entries.Add(new DirectoryEntry(dir.Name, FullPath + '/' + dir.Name, DirectoryEntryType.Directory, 0));
}
}
if (Mode.HasFlag(OpenDirectoryMode.Files))
{
foreach (FileInfo file in DirInfo.EnumerateFiles())
{
entries.Add(new DirectoryEntry(file.Name, FullPath + '/' + file.Name, DirectoryEntryType.File, file.Length));
}
}
return entries.ToArray();
}
public int GetEntryCount()
{
int count = 0;
if (Mode.HasFlag(OpenDirectoryMode.Directories))
{
count += Directory.EnumerateDirectories(LocalPath).Count();
}
if (Mode.HasFlag(OpenDirectoryMode.Files))
{
count += Directory.EnumerateFiles(LocalPath).Count();
}
return count;
}
}
}

View file

@ -0,0 +1,49 @@
using System;
using System.IO;
namespace LibHac.IO
{
public class LocalFile : FileBase
{
private string Path { get; }
private StreamStorage Storage { get; }
public LocalFile(string path, OpenMode mode)
{
Path = path;
Mode = mode;
Storage = new StreamStorage(new FileStream(Path, FileMode.Open), false);
}
public override int Read(Span<byte> destination, long offset)
{
int toRead = ValidateReadParamsAndGetSize(destination, offset);
Storage.Read(destination.Slice(0, toRead), offset);
return toRead;
}
public override void Write(ReadOnlySpan<byte> source, long offset)
{
ValidateWriteParams(source, offset);
Storage.Write(source, offset);
}
public override void Flush()
{
Storage.Flush();
}
public override long GetSize()
{
return Storage.Length;
}
public override void SetSize(long size)
{
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,116 @@
using System;
using System.IO;
namespace LibHac.IO
{
public class LocalFileSystem : IFileSystem
{
private string BasePath { get; }
public LocalFileSystem(string basePath)
{
BasePath = Path.GetFullPath(basePath);
}
internal string ResolveLocalPath(string path)
{
return Path.Combine(BasePath, path.TrimStart('/'));
}
public void CreateDirectory(string path)
{
path = PathTools.Normalize(path);
Directory.CreateDirectory(ResolveLocalPath(path));
}
public void CreateFile(string path, long size)
{
path = PathTools.Normalize(path);
string localPath = ResolveLocalPath(path);
string localDir = Path.GetDirectoryName(localPath);
if (localDir != null) Directory.CreateDirectory(localDir);
using (FileStream stream = File.Create(localPath))
{
stream.SetLength(size);
}
}
public void DeleteDirectory(string path)
{
path = PathTools.Normalize(path);
string resolveLocalPath = ResolveLocalPath(path);
Directory.Delete(resolveLocalPath);
}
public void DeleteFile(string path)
{
path = PathTools.Normalize(path);
string resolveLocalPath = ResolveLocalPath(path);
File.Delete(resolveLocalPath);
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
{
path = PathTools.Normalize(path);
return new LocalDirectory(this, path, mode);
}
public IFile OpenFile(string path, OpenMode mode)
{
path = PathTools.Normalize(path);
string localPath = ResolveLocalPath(path);
return new LocalFile(localPath, mode);
}
public void RenameDirectory(string srcPath, string dstPath)
{
srcPath = PathTools.Normalize(srcPath);
dstPath = PathTools.Normalize(dstPath);
string srcLocalPath = ResolveLocalPath(srcPath);
string dstLocalPath = ResolveLocalPath(dstPath);
string directoryName = Path.GetDirectoryName(dstLocalPath);
if (directoryName != null) Directory.CreateDirectory(directoryName);
Directory.Move(srcLocalPath, dstLocalPath);
}
public void RenameFile(string srcPath, string dstPath)
{
srcPath = PathTools.Normalize(srcPath);
dstPath = PathTools.Normalize(dstPath);
string srcLocalPath = ResolveLocalPath(srcPath);
string dstLocalPath = ResolveLocalPath(dstPath);
string dstLocalDir = Path.GetDirectoryName(dstLocalPath);
if (dstLocalDir != null) Directory.CreateDirectory(dstLocalDir);
File.Move(srcLocalPath, dstLocalPath);
}
public bool DirectoryExists(string path)
{
path = PathTools.Normalize(path);
return Directory.Exists(ResolveLocalPath(path));
}
public bool FileExists(string path)
{
path = PathTools.Normalize(path);
return File.Exists(ResolveLocalPath(path));
}
public void Commit()
{
throw new NotImplementedException();
}
}
}

View file

@ -1,16 +1,20 @@
using System.IO; using System.Collections.Generic;
using System.IO;
namespace LibHac.IO namespace LibHac.IO
{ {
public class RomFsDirectory : IDirectory public class RomFsDirectory : IDirectory
{ {
public IFileSystem ParentFileSystem { get; } public IFileSystem ParentFileSystem { get; }
public string FullPath { get; }
private RomfsDir Directory { get; } private RomfsDir Directory { get; }
private OpenDirectoryMode Mode { get; } private OpenDirectoryMode Mode { get; }
public RomFsDirectory(RomFsFileSystem fs, string path, OpenDirectoryMode mode) public RomFsDirectory(RomFsFileSystem fs, string path, OpenDirectoryMode mode)
{ {
path = PathTools.Normalize(path);
if (!fs.DirectoryDict.TryGetValue(path, out RomfsDir dir)) if (!fs.DirectoryDict.TryGetValue(path, out RomfsDir dir))
{ {
throw new DirectoryNotFoundException(path); throw new DirectoryNotFoundException(path);
@ -18,25 +22,20 @@ namespace LibHac.IO
ParentFileSystem = fs; ParentFileSystem = fs;
Directory = dir; Directory = dir;
FullPath = path;
Mode = mode; Mode = mode;
} }
public DirectoryEntry[] Read() public IEnumerable<DirectoryEntry> Read()
{ {
int count = GetEntryCount();
var entries = new DirectoryEntry[count];
int index = 0;
if (Mode.HasFlag(OpenDirectoryMode.Directories)) if (Mode.HasFlag(OpenDirectoryMode.Directories))
{ {
RomfsDir dirEntry = Directory.FirstChild; RomfsDir dirEntry = Directory.FirstChild;
while (dirEntry != null) while (dirEntry != null)
{ {
entries[index] = new DirectoryEntry(dirEntry.FullPath, DirectoryEntryType.Directory, 0); yield return new DirectoryEntry(dirEntry.Name, FullPath + '/' + dirEntry.Name, DirectoryEntryType.Directory, 0);
dirEntry = dirEntry.NextSibling; dirEntry = dirEntry.NextSibling;
index++;
} }
} }
@ -46,14 +45,10 @@ namespace LibHac.IO
while (fileEntry != null) while (fileEntry != null)
{ {
entries[index] = yield return new DirectoryEntry(fileEntry.Name, FullPath + '/' + fileEntry.Name, DirectoryEntryType.File, fileEntry.DataLength);
new DirectoryEntry(fileEntry.FullPath, DirectoryEntryType.File, fileEntry.DataLength);
fileEntry = fileEntry.NextSibling; fileEntry = fileEntry.NextSibling;
index++;
} }
} }
return entries;
} }
public int GetEntryCount() public int GetEntryCount()

View file

@ -10,6 +10,7 @@ namespace LibHac.IO
public RomFsFile(IStorage baseStorage, long offset, long size) public RomFsFile(IStorage baseStorage, long offset, long size)
{ {
Mode = OpenMode.Read;
BaseStorage = baseStorage; BaseStorage = baseStorage;
Offset = offset; Offset = offset;
Size = size; Size = size;
@ -17,9 +18,9 @@ namespace LibHac.IO
public override int Read(Span<byte> destination, long offset) public override int Read(Span<byte> destination, long offset)
{ {
long storageOffset = Offset + offset; int toRead = ValidateReadParamsAndGetSize(destination, offset);
int toRead = GetAvailableSizeAndValidate(destination, offset);
long storageOffset = Offset + offset;
BaseStorage.Read(destination.Slice(0, toRead), storageOffset); BaseStorage.Read(destination.Slice(0, toRead), storageOffset);
return toRead; return toRead;
@ -32,7 +33,6 @@ namespace LibHac.IO
public override void Flush() public override void Flush()
{ {
throw new NotImplementedException();
} }
public override long GetSize() public override long GetSize()
@ -40,7 +40,7 @@ namespace LibHac.IO
return Size; return Size;
} }
public override long SetSize() public override void SetSize(long size)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }

View file

@ -115,13 +115,18 @@ namespace LibHac.IO
return new RomFsDirectory(this, path, mode); return new RomFsDirectory(this, path, mode);
} }
public IFile OpenFile(string path) public IFile OpenFile(string path, OpenMode mode)
{ {
if (!FileDict.TryGetValue(path, out RomfsFile file)) if (!FileDict.TryGetValue(path, out RomfsFile file))
{ {
throw new FileNotFoundException(); throw new FileNotFoundException();
} }
if (mode != OpenMode.Read)
{
throw new ArgumentOutOfRangeException(nameof(mode), "RomFs files must be opened read-only.");
}
return OpenFile(file); return OpenFile(file);
} }