From 3bf488ce520a81811bf6e949e2153aabf4b713ea Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Fri, 3 Aug 2018 11:46:30 -0400
Subject: [PATCH] vfs: Add VfsFilesystem interface and default implementation

---
 src/core/file_sys/vfs.cpp | 148 ++++++++++++++++++++++++++++++++++++++
 src/core/file_sys/vfs.h   |  66 ++++++++++++++++-
 2 files changed, 211 insertions(+), 3 deletions(-)

diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index dae1c16ef0..24e1589626 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -4,12 +4,160 @@
 
 #include <algorithm>
 #include <numeric>
+#include <string>
+#include "common/common_paths.h"
 #include "common/file_util.h"
 #include "common/logging/backend.h"
 #include "core/file_sys/vfs.h"
 
 namespace FileSys {
 
+VfsFilesystem::VfsFilesystem(VirtualDir root_) : root(std::move(root_)) {}
+
+VfsFilesystem::~VfsFilesystem() = default;
+
+std::string VfsFilesystem::GetName() const {
+    return root->GetName();
+}
+
+bool VfsFilesystem::IsReadable() const {
+    return root->IsReadable();
+}
+
+bool VfsFilesystem::IsWritable() const {
+    return root->IsWritable();
+}
+
+VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
+    const auto path = FileUtil::SanitizePath(path_);
+    if (root->GetFileRelative(path) != nullptr)
+        return VfsEntryType::File;
+    if (root->GetDirectoryRelative(path) != nullptr)
+        return VfsEntryType::Directory;
+
+    return VfsEntryType::None;
+}
+
+VirtualFile VfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
+    const auto path = FileUtil::SanitizePath(path_);
+    return root->GetFileRelative(path);
+}
+
+VirtualFile VfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
+    const auto path = FileUtil::SanitizePath(path_);
+    return root->CreateFileRelative(path);
+}
+
+VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
+    const auto old_path = FileUtil::SanitizePath(old_path_);
+    const auto new_path = FileUtil::SanitizePath(new_path_);
+
+    // VfsDirectory impls are only required to implement copy across the current directory.
+    if (FileUtil::GetParentPath(old_path) == FileUtil::GetParentPath(new_path)) {
+        if (!root->Copy(FileUtil::GetFilename(old_path), FileUtil::GetFilename(new_path)))
+            return nullptr;
+        return OpenFile(new_path, Mode::ReadWrite);
+    }
+
+    // Do it using RawCopy. Non-default impls are encouraged to optimize this.
+    const auto old_file = OpenFile(old_path, Mode::Read);
+    if (old_file == nullptr)
+        return nullptr;
+    auto new_file = OpenFile(new_path, Mode::Read);
+    if (new_file != nullptr)
+        return nullptr;
+    new_file = CreateFile(new_path, Mode::Write);
+    if (new_file == nullptr)
+        return nullptr;
+    if (!VfsRawCopy(old_file, new_file))
+        return nullptr;
+    return new_file;
+}
+
+VirtualFile VfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
+    const auto old_path = FileUtil::SanitizePath(old_path_);
+    const auto new_path = FileUtil::SanitizePath(new_path_);
+
+    // Again, non-default impls are highly encouraged to provide a more optimized version of this.
+    auto out = CopyFile(old_path_, new_path_);
+    if (out == nullptr)
+        return nullptr;
+    if (DeleteFile(old_path))
+        return out;
+    return nullptr;
+}
+
+bool VfsFilesystem::DeleteFile(std::string_view path_) {
+    const auto path = FileUtil::SanitizePath(path_);
+    auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write);
+    if (parent == nullptr)
+        return false;
+    return parent->DeleteFile(FileUtil::GetFilename(path));
+}
+
+VirtualDir VfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
+    const auto path = FileUtil::SanitizePath(path_);
+    return root->GetDirectoryRelative(path);
+}
+
+VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
+    const auto path = FileUtil::SanitizePath(path_);
+    return root->CreateDirectoryRelative(path);
+}
+
+VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_view new_path_) {
+    const auto old_path = FileUtil::SanitizePath(old_path_);
+    const auto new_path = FileUtil::SanitizePath(new_path_);
+
+    // Non-default impls are highly encouraged to provide a more optimized version of this.
+    auto old_dir = OpenDirectory(old_path, Mode::Read);
+    if (old_dir == nullptr)
+        return nullptr;
+    auto new_dir = OpenDirectory(new_path, Mode::Read);
+    if (new_dir != nullptr)
+        return nullptr;
+    new_dir = CreateDirectory(new_path, Mode::Write);
+    if (new_dir == nullptr)
+        return nullptr;
+
+    for (const auto& file : old_dir->GetFiles()) {
+        const auto x =
+            CopyFile(old_path + DIR_SEP + file->GetName(), new_path + DIR_SEP + file->GetName());
+        if (x == nullptr)
+            return nullptr;
+    }
+
+    for (const auto& dir : old_dir->GetSubdirectories()) {
+        const auto x =
+            CopyDirectory(old_path + DIR_SEP + dir->GetName(), new_path + DIR_SEP + dir->GetName());
+        if (x == nullptr)
+            return nullptr;
+    }
+
+    return new_dir;
+}
+
+VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path_, std::string_view new_path_) {
+    const auto old_path = FileUtil::SanitizePath(old_path_);
+    const auto new_path = FileUtil::SanitizePath(new_path_);
+
+    // Non-default impls are highly encouraged to provide a more optimized version of this.
+    auto out = CopyDirectory(old_path_, new_path_);
+    if (out == nullptr)
+        return nullptr;
+    if (DeleteDirectory(old_path))
+        return out;
+    return nullptr;
+}
+
+bool VfsFilesystem::DeleteDirectory(std::string_view path_) {
+    const auto path = FileUtil::SanitizePath(path_);
+    auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write);
+    if (parent == nullptr)
+        return false;
+    return parent->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path));
+}
+
 VfsFile::~VfsFile() = default;
 
 std::string VfsFile::GetExtension() const {
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h
index fab9e2b45f..9c7ef93b81 100644
--- a/src/core/file_sys/vfs.h
+++ b/src/core/file_sys/vfs.h
@@ -11,14 +11,74 @@
 #include <vector>
 #include "boost/optional.hpp"
 #include "common/common_types.h"
+#include "core/file_sys/mode.h"
 
 namespace FileSys {
+
+struct VfsFilesystem;
 struct VfsFile;
 struct VfsDirectory;
 
-// Convenience typedefs to use VfsDirectory and VfsFile
-using VirtualDir = std::shared_ptr<FileSys::VfsDirectory>;
-using VirtualFile = std::shared_ptr<FileSys::VfsFile>;
+// Convenience typedefs to use Vfs* interfaces
+using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
+using VirtualDir = std::shared_ptr<VfsDirectory>;
+using VirtualFile = std::shared_ptr<VfsFile>;
+
+// An enumeration representing what can be at the end of a path in a VfsFilesystem
+enum class VfsEntryType {
+    None,
+    File,
+    Directory,
+};
+
+// A class represnting an abstract filesystem. A default implementation given the root VirtualDir is
+// provided for convenience, but if the Vfs implementation has any additional state or
+// functionality, they will need to override.
+struct VfsFilesystem : NonCopyable {
+    VfsFilesystem(VirtualDir root);
+    virtual ~VfsFilesystem();
+
+    // Gets the friendly name for the filesystem.
+    virtual std::string GetName() const;
+
+    // Return whether or not the user has read permissions on this filesystem.
+    virtual bool IsReadable() const;
+    // Return whether or not the user has write permission on this filesystem.
+    virtual bool IsWritable() const;
+
+    // Determine if the entry at path is non-existant, a file, or a directory.
+    virtual VfsEntryType GetEntryType(std::string_view path) const;
+
+    // Opens the file with path relative to root. If it doesn't exist, returns nullptr.
+    virtual VirtualFile OpenFile(std::string_view path, Mode perms);
+    // Creates a new, empty file at path
+    virtual VirtualFile CreateFile(std::string_view path, Mode perms);
+    // Copies the file from old_path to new_path, returning the new file on success and nullptr on
+    // failure.
+    virtual VirtualFile CopyFile(std::string_view old_path, std::string_view new_path);
+    // Moves the file from old_path to new_path, returning the moved file on success and nullptr on
+    // failure.
+    virtual VirtualFile MoveFile(std::string_view old_path, std::string_view new_path);
+    // Deletes the file with path relative to root, returing true on success.
+    virtual bool DeleteFile(std::string_view path);
+
+    // Opens the directory with path relative to root. If it doesn't exist, returns nullptr.
+    virtual VirtualDir OpenDirectory(std::string_view path, Mode perms);
+    // Creates a new, empty directory at path
+    virtual VirtualDir CreateDirectory(std::string_view path, Mode perms);
+    // Copies the directory from old_path to new_path, returning the new directory on success and
+    // nullptr on failure.
+    virtual VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path);
+    // Moves the directory from old_path to new_path, returning the moved directory on success and
+    // nullptr on failure.
+    virtual VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path);
+    // Deletes the directory with path relative to root, returing true on success.
+    virtual bool DeleteDirectory(std::string_view path);
+
+protected:
+    // Root directory in default implementation.
+    VirtualDir root;
+};
 
 // A class representing a file in an abstract filesystem.
 struct VfsFile : NonCopyable {