diff --git a/src/LibHac/Fs/DirectorySaveDataFile.cs b/src/LibHac/Fs/DirectorySaveDataFile.cs new file mode 100644 index 00000000..2f0bd6c4 --- /dev/null +++ b/src/LibHac/Fs/DirectorySaveDataFile.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LibHac.Fs +{ + public class DirectorySaveDataFile : FileBase + { + private IFile BaseFile { get; } + private DirectorySaveDataFileSystem ParentFs { get; } + private object DisposeLocker { get; } = new object(); + + public DirectorySaveDataFile(DirectorySaveDataFileSystem parentFs, IFile baseFile) + { + ParentFs = parentFs; + BaseFile = baseFile; + Mode = BaseFile.Mode; + ToDispose.Add(BaseFile); + } + + public override int Read(Span destination, long offset) + { + return BaseFile.Read(destination, offset); + } + + public override void Write(ReadOnlySpan source, long offset) + { + BaseFile.Write(source, offset); + } + + public override void Flush() + { + BaseFile.Flush(); + } + + public override long GetSize() + { + return BaseFile.GetSize(); + } + + public override void SetSize(long size) + { + BaseFile.SetSize(size); + } + + protected override void Dispose(bool disposing) + { + lock (DisposeLocker) + { + if (IsDisposed) return; + + base.Dispose(disposing); + + if (Mode.HasFlag(OpenMode.Write)) + { + ParentFs.NotifyCloseWritableFile(); + } + } + } + } +} diff --git a/src/LibHac/Fs/DirectorySaveDataFileSystem.cs b/src/LibHac/Fs/DirectorySaveDataFileSystem.cs new file mode 100644 index 00000000..5997fb36 --- /dev/null +++ b/src/LibHac/Fs/DirectorySaveDataFileSystem.cs @@ -0,0 +1,238 @@ +using System; + +namespace LibHac.Fs +{ + public class DirectorySaveDataFileSystem : IFileSystem + { + private const string CommittedDir = "/0/"; + private const string WorkingDir = "/1/"; + private const string SyncDir = "/_/"; + + private IFileSystem BaseFs { get; } + private object Locker { get; } = new object(); + private int OpenWritableFileCount { get; set; } + + public DirectorySaveDataFileSystem(IFileSystem baseFileSystem) + { + BaseFs = baseFileSystem; + + if (!BaseFs.DirectoryExists(WorkingDir)) + { + BaseFs.CreateDirectory(WorkingDir); + BaseFs.CreateDirectory(CommittedDir); + } + + if (BaseFs.DirectoryExists(CommittedDir)) + { + SynchronizeDirectory(WorkingDir, CommittedDir); + } + else + { + SynchronizeDirectory(SyncDir, WorkingDir); + BaseFs.RenameDirectory(SyncDir, CommittedDir); + } + } + + public void CreateDirectory(string path) + { + string fullPath = GetFullPath(PathTools.Normalize(path)); + + lock (Locker) + { + BaseFs.CreateDirectory(path); + } + } + + public void CreateFile(string path, long size, CreateFileOptions options) + { + string fullPath = GetFullPath(PathTools.Normalize(path)); + + lock (Locker) + { + BaseFs.CreateFile(fullPath, size, options); + } + } + + public void DeleteDirectory(string path) + { + string fullPath = GetFullPath(PathTools.Normalize(path)); + + lock (Locker) + { + BaseFs.DeleteDirectory(fullPath); + } + } + + public void DeleteDirectoryRecursively(string path) + { + string fullPath = GetFullPath(PathTools.Normalize(path)); + + lock (Locker) + { + BaseFs.DeleteDirectoryRecursively(fullPath); + } + } + + public void CleanDirectoryRecursively(string path) + { + string fullPath = GetFullPath(PathTools.Normalize(path)); + + lock (Locker) + { + BaseFs.CleanDirectoryRecursively(fullPath); + } + } + + public void DeleteFile(string path) + { + string fullPath = GetFullPath(PathTools.Normalize(path)); + + lock (Locker) + { + BaseFs.DeleteFile(fullPath); + } + } + + public IDirectory OpenDirectory(string path, OpenDirectoryMode mode) + { + string fullPath = GetFullPath(PathTools.Normalize(path)); + + lock (Locker) + { + return BaseFs.OpenDirectory(fullPath, mode); + } + } + + public IFile OpenFile(string path, OpenMode mode) + { + string fullPath = GetFullPath(PathTools.Normalize(path)); + + lock (Locker) + { + IFile baseFile = BaseFs.OpenFile(fullPath, mode); + var file = new DirectorySaveDataFile(this, baseFile); + + if (mode.HasFlag(OpenMode.Write)) + { + OpenWritableFileCount++; + } + + return file; + } + } + + public void RenameDirectory(string srcPath, string dstPath) + { + string fullSrcPath = GetFullPath(PathTools.Normalize(srcPath)); + string fullDstPath = GetFullPath(PathTools.Normalize(dstPath)); + + lock (Locker) + { + BaseFs.RenameDirectory(fullSrcPath, fullDstPath); + } + } + + public void RenameFile(string srcPath, string dstPath) + { + string fullSrcPath = GetFullPath(PathTools.Normalize(srcPath)); + string fullDstPath = GetFullPath(PathTools.Normalize(dstPath)); + + lock (Locker) + { + BaseFs.RenameFile(fullSrcPath, fullDstPath); + } + } + + public bool DirectoryExists(string path) + { + string fullPath = GetFullPath(PathTools.Normalize(path)); + + lock (Locker) + { + return BaseFs.DirectoryExists(fullPath); + } + } + + public bool FileExists(string path) + { + string fullPath = GetFullPath(PathTools.Normalize(path)); + + lock (Locker) + { + return BaseFs.FileExists(fullPath); + } + } + + public DirectoryEntryType GetEntryType(string path) + { + string fullPath = GetFullPath(PathTools.Normalize(path)); + + lock (Locker) + { + return BaseFs.GetEntryType(fullPath); + } + } + + public long GetFreeSpaceSize(string path) + { + throw new NotSupportedException(); + } + + public long GetTotalSpaceSize(string path) + { + throw new NotSupportedException(); + } + + public FileTimeStampRaw GetFileTimeStampRaw(string path) + { + throw new NotSupportedException(); + } + + public void Commit() + { + if (OpenWritableFileCount > 0) + { + throw new InvalidOperationException("All files must be closed before commiting save data."); + } + + SynchronizeDirectory(SyncDir, WorkingDir); + + BaseFs.DeleteDirectoryRecursively(CommittedDir); + + BaseFs.RenameDirectory(SyncDir, CommittedDir); + } + + public void QueryEntry(Span outBuffer, ReadOnlySpan inBuffer, string path, QueryId queryId) + { + throw new NotSupportedException(); + } + + private string GetFullPath(string path) + { + return PathTools.Normalize(PathTools.Combine(WorkingDir, path)); + } + + private void SynchronizeDirectory(string dest, string src) + { + if (BaseFs.DirectoryExists(dest)) + { + BaseFs.DeleteDirectoryRecursively(dest); + } + + BaseFs.CreateDirectory(dest); + + IDirectory sourceDir = BaseFs.OpenDirectory(src, OpenDirectoryMode.All); + IDirectory destDir = BaseFs.OpenDirectory(dest, OpenDirectoryMode.All); + + sourceDir.CopyDirectory(destDir); + } + + internal void NotifyCloseWritableFile() + { + lock (Locker) + { + OpenWritableFileCount--; + } + } + } +} diff --git a/src/LibHac/Fs/FileBase.cs b/src/LibHac/Fs/FileBase.cs index 8f6404ad..f63b2f93 100644 --- a/src/LibHac/Fs/FileBase.cs +++ b/src/LibHac/Fs/FileBase.cs @@ -5,7 +5,7 @@ namespace LibHac.Fs { public abstract class FileBase : IFile { - private bool _isDisposed; + protected bool IsDisposed { get; private set; } internal List ToDispose { get; } = new List(); public abstract int Read(Span destination, long offset); @@ -18,7 +18,7 @@ namespace LibHac.Fs protected int ValidateReadParamsAndGetSize(ReadOnlySpan span, long offset) { - if (_isDisposed) throw new ObjectDisposedException(null); + 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)); @@ -34,7 +34,7 @@ namespace LibHac.Fs protected void ValidateWriteParams(ReadOnlySpan span, long offset) { - if (_isDisposed) throw new ObjectDisposedException(null); + if (IsDisposed) throw new ObjectDisposedException(null); if ((Mode & OpenMode.Write) == 0) throw new NotSupportedException("File does not allow writing."); @@ -63,7 +63,7 @@ namespace LibHac.Fs protected virtual void Dispose(bool disposing) { - if (_isDisposed) return; + if (IsDisposed) return; if (disposing) { @@ -75,7 +75,7 @@ namespace LibHac.Fs } } - _isDisposed = true; + IsDisposed = true; } } diff --git a/src/LibHac/Fs/LocalFile.cs b/src/LibHac/Fs/LocalFile.cs index 6ea5d664..43c215fc 100644 --- a/src/LibHac/Fs/LocalFile.cs +++ b/src/LibHac/Fs/LocalFile.cs @@ -13,7 +13,7 @@ namespace LibHac.Fs { Path = path; Mode = mode; - Stream = new FileStream(Path, FileMode.Open, GetFileAccess(mode)); + Stream = new FileStream(Path, FileMode.Open, GetFileAccess(mode), GetFileShare(mode)); File = new StreamFile(Stream, mode); ToDispose.Add(File); @@ -56,5 +56,10 @@ namespace LibHac.Fs // FileAccess and OpenMode have the same flags return (FileAccess)(mode & OpenMode.ReadWrite); } + + private static FileShare GetFileShare(OpenMode mode) + { + return mode.HasFlag(OpenMode.Write) ? FileShare.Read : FileShare.ReadWrite; + } } } diff --git a/src/LibHac/Fs/LocalFileSystem.cs b/src/LibHac/Fs/LocalFileSystem.cs index e06c7728..fb6ba75d 100644 --- a/src/LibHac/Fs/LocalFileSystem.cs +++ b/src/LibHac/Fs/LocalFileSystem.cs @@ -61,7 +61,7 @@ namespace LibHac.Fs { path = PathTools.Normalize(path); string localPath = ResolveLocalPath(path); - string localDir = Path.GetDirectoryName(localPath); + string localDir = ResolveLocalPath(PathTools.GetParentDirectory(path)); if (localDir != null) Directory.CreateDirectory(localDir); @@ -132,7 +132,7 @@ namespace LibHac.Fs string srcLocalPath = ResolveLocalPath(srcPath); string dstLocalPath = ResolveLocalPath(dstPath); - string directoryName = Path.GetDirectoryName(dstLocalPath); + string directoryName = ResolveLocalPath(PathTools.GetParentDirectory(dstPath)); if (directoryName != null) Directory.CreateDirectory(directoryName); Directory.Move(srcLocalPath, dstLocalPath); } @@ -144,7 +144,7 @@ namespace LibHac.Fs string srcLocalPath = ResolveLocalPath(srcPath); string dstLocalPath = ResolveLocalPath(dstPath); - string dstLocalDir = Path.GetDirectoryName(dstLocalPath); + string dstLocalDir = ResolveLocalPath(PathTools.GetParentDirectory(dstPath)); if (dstLocalDir != null) Directory.CreateDirectory(dstLocalDir); File.Move(srcLocalPath, dstLocalPath);