diff --git a/CMakeLists.txt b/CMakeLists.txt index 1237432d..90bc2957 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,6 +225,8 @@ set(COMMON src/common/logging/backend.cpp src/common/io_file.h src/common/error.cpp src/common/error.h + src/common/fs.cpp + src/common/fs.h src/common/scope_exit.h src/common/func_traits.h src/common/native_clock.cpp diff --git a/src/common/fs.cpp b/src/common/fs.cpp new file mode 100644 index 00000000..e94ad25d --- /dev/null +++ b/src/common/fs.cpp @@ -0,0 +1,666 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "fs.h" +#include "io_file.h" + +#include "logging/log.h" +#include "path_util.h" + +namespace Common::FS { + +namespace fs = std::filesystem; + +// File Operations + +bool NewFile(const fs::path& path, u64 size) { + if (!ValidatePath(path)) { + // LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path.parent_path())) { + // LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist", + // PathToUTF8String(path)); + return false; + } + + if (Exists(path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at path={} exists", + // PathToUTF8String(path)); + return false; + } + + IOFile io_file{path, FileAccessMode::Write}; + + if (!io_file.IsOpen()) { + // LOG_ERROR(Common_Filesystem, "Failed to create a file at path={}", + // PathToUTF8String(path)); + return false; + } + + if (!io_file.SetSize(size)) { + // LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={} to size={}", + // PathToUTF8String(path), size); + return false; + } + + io_file.Close(); + + // LOG_DEBUG(Common_Filesystem, "Successfully created a file at path={} with size={}", + // PathToUTF8String(path), size); + + return true; +} + +bool RemoveFile(const fs::path& path) { + if (!ValidatePath(path)) { + // LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", + // PathToUTF8String(path)); + return false; + } + + if (!Exists(path)) { + // LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", + // PathToUTF8String(path)); + return true; + } + + if (!IsFile(path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file", + // PathToUTF8String(path)); + return false; + } + + std::error_code ec; + + fs::remove(path, ec); + + if (ec) { + // LOG_ERROR(Common_Filesystem, "Failed to remove the file at path={}, ec_message={}", + // PathToUTF8String(path), ec.message()); + return false; + } + + // LOG_DEBUG(Common_Filesystem, "Successfully removed the file at path={}", + // PathToUTF8String(path)); + + return true; +} + +bool RenameFile(const fs::path& old_path, const fs::path& new_path) { + if (!ValidatePath(old_path) || !ValidatePath(new_path)) { + // LOG_ERROR(Common_Filesystem, + // "One or both input path(s) is not valid, old_path={}, new_path={}", + // PathToUTF8String(old_path), PathToUTF8String(new_path)); + return false; + } + + if (!Exists(old_path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} does not exist", + // PathToUTF8String(old_path)); + return false; + } + + if (!IsFile(old_path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} is not a file", + // PathToUTF8String(old_path)); + return false; + } + + if (Exists(new_path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} exists", + // PathToUTF8String(new_path)); + return false; + } + + std::error_code ec; + + fs::rename(old_path, new_path, ec); + + if (ec) { + // LOG_ERROR(Common_Filesystem, + // "Failed to rename the file from old_path={} to new_path={}, ec_message={}", + // PathToUTF8String(old_path), PathToUTF8String(new_path), ec.message()); + return false; + } + + // LOG_DEBUG(Common_Filesystem, "Successfully renamed the file from old_path={} to new_path={}", + // PathToUTF8String(old_path), PathToUTF8String(new_path)); + + return true; +} + +std::shared_ptr FileOpen(const fs::path& path, FileAccessMode mode, FileType type, + FileShareFlag flag) { + if (!ValidatePath(path)) { + // LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return nullptr; + } + + if (Exists(path) && !IsFile(path)) { + // LOG_ERROR(Common_Filesystem, + // "Filesystem object at path={} exists and is not a regular file", + // PathToUTF8String(path)); + return nullptr; + } + + auto io_file = std::make_shared(path, mode, type, flag); + + if (!io_file->IsOpen()) { + io_file.reset(); + + // LOG_ERROR(Common_Filesystem, + // "Failed to open the file at path={} with mode={}, type={}, flag={}", + // PathToUTF8String(path), mode, type, flag); + + return nullptr; + } + + // LOG_DEBUG(Common_Filesystem, + // "Successfully opened the file at path={} with mode={}, type={}, flag={}", + // PathToUTF8String(path), mode, type, flag); + + return io_file; +} + +// Directory Operations + +bool CreateDir(const fs::path& path) { + if (!ValidatePath(path)) { + // LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", + // PathToUTF8String(path)); + return false; + } + + if (!Exists(path.parent_path())) { + // LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist", + // PathToUTF8String(path)); + return false; + } + + if (IsDir(path)) { + // LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory", + // PathToUTF8String(path)); + return true; + } + + std::error_code ec; + + fs::create_directory(path, ec); + + if (ec) { + // LOG_ERROR(Common_Filesystem, "Failed to create the directory at path={}, ec_message={}", + // PathToUTF8String(path), ec.message()); + return false; + } + + // LOG_DEBUG(Common_Filesystem, "Successfully created the directory at path={}", + // PathToUTF8String(path)); + + return true; +} + +bool CreateDirs(const fs::path& path) { + if (!ValidatePath(path)) { + // LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (IsDir(path)) { + // LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory", + // PathToUTF8String(path)); + return true; + } + + std::error_code ec; + + fs::create_directories(path, ec); + + if (ec) { + // LOG_ERROR(Common_Filesystem, "Failed to create the directories at path={}, + // ec_message={}", + // PathToUTF8String(path), ec.message()); + return false; + } + + // LOG_DEBUG(Common_Filesystem, "Successfully created the directories at path={}", + // PathToUTF8String(path)); + + return true; +} + +bool CreateParentDir(const fs::path& path) { + return CreateDir(path.parent_path()); +} + +bool CreateParentDirs(const fs::path& path) { + return CreateDirs(path.parent_path()); +} + +bool RemoveDir(const fs::path& path) { + if (!ValidatePath(path)) { + // LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path)) { + // LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", + // PathToUTF8String(path)); + return true; + } + + if (!IsDir(path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + // PathToUTF8String(path)); + return false; + } + + std::error_code ec; + + fs::remove(path, ec); + + if (ec) { + // LOG_ERROR(Common_Filesystem, "Failed to remove the directory at path={}, ec_message={}", + // PathToUTF8String(path), ec.message()); + return false; + } + + // LOG_DEBUG(Common_Filesystem, "Successfully removed the directory at path={}", + // PathToUTF8String(path)); + + return true; +} + +bool RemoveDirRecursively(const fs::path& path) { + if (!ValidatePath(path)) { + // LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path)) { + // LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", + // PathToUTF8String(path)); + return true; + } + + if (!IsDir(path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + // PathToUTF8String(path)); + return false; + } + + std::error_code ec; + + fs::remove_all(path, ec); + + if (ec) { + // LOG_ERROR(Common_Filesystem, + // "Failed to remove the directory and its contents at path={}, ec_message={}", + // PathToUTF8String(path), ec.message()); + return false; + } + + // LOG_DEBUG(Common_Filesystem, "Successfully removed the directory and its contents at + // path={}", + // PathToUTF8String(path)); + + return true; +} + +bool RemoveDirContentsRecursively(const fs::path& path) { + if (!ValidatePath(path)) { + // LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", + // PathToUTF8String(path)); + return false; + } + + if (!Exists(path)) { + // LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", + // PathToUTF8String(path)); + return true; + } + + if (!IsDir(path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + // PathToUTF8String(path)); + return false; + } + + std::error_code ec; + + // TODO (Morph): Replace this with recursive_directory_iterator once it's fixed in MSVC. + for (const auto& entry : fs::directory_iterator(path, ec)) { + if (ec) { + // LOG_ERROR(Common_Filesystem, + // "Failed to completely enumerate the directory at path={}, ec_message={}", + // PathToUTF8String(path), ec.message()); + break; + } + + fs::remove(entry.path(), ec); + + if (ec) { + // LOG_ERROR(Common_Filesystem, + // "Failed to remove the filesystem object at path={}, ec_message={}", + // PathToUTF8String(entry.path()), ec.message()); + break; + } + + // TODO (Morph): Remove this when MSVC fixes recursive_directory_iterator. + // recursive_directory_iterator throws an exception despite passing in a std::error_code. + if (entry.status().type() == fs::file_type::directory) { + return RemoveDirContentsRecursively(entry.path()); + } + } + + if (ec) { + // LOG_ERROR(Common_Filesystem, + // "Failed to remove all the contents of the directory at path={}, ec_message={}", + // PathToUTF8String(path), ec.message()); + return false; + } + + // LOG_DEBUG(Common_Filesystem, + // "Successfully removed all the contents of the directory at path={}", + // PathToUTF8String(path)); + + return true; +} + +bool RenameDir(const fs::path& old_path, const fs::path& new_path) { + if (!ValidatePath(old_path) || !ValidatePath(new_path)) { + // LOG_ERROR(Common_Filesystem, + // "One or both input path(s) is not valid, old_path={}, new_path={}", + // PathToUTF8String(old_path), PathToUTF8String(new_path)); + return false; + } + + if (!Exists(old_path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} does not exist", + // PathToUTF8String(old_path)); + return false; + } + + if (!IsDir(old_path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} is not a directory", + // PathToUTF8String(old_path)); + return false; + } + + if (Exists(new_path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} exists", + // PathToUTF8String(new_path)); + return false; + } + + std::error_code ec; + + fs::rename(old_path, new_path, ec); + + if (ec) { + // LOG_ERROR(Common_Filesystem, + // "Failed to rename the file from old_path={} to new_path={}, ec_message={}", + // PathToUTF8String(old_path), PathToUTF8String(new_path), ec.message()); + return false; + } + + // LOG_DEBUG(Common_Filesystem, "Successfully renamed the file from old_path={} to new_path={}", + // PathToUTF8String(old_path), PathToUTF8String(new_path)); + + return true; +} + +void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback, + DirEntryFilter filter) { + if (!ValidatePath(path)) { + // LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return; + } + + if (!Exists(path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist", + // PathToUTF8String(path)); + return; + } + + if (!IsDir(path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + // PathToUTF8String(path)); + return; + } + + bool callback_error = false; + + std::error_code ec; + + for (const auto& entry : fs::directory_iterator(path, ec)) { + if (ec) { + break; + } + + if (True(filter & DirEntryFilter::File) && + entry.status().type() == fs::file_type::regular) { + if (!callback(entry)) { + callback_error = true; + break; + } + } + + if (True(filter & DirEntryFilter::Directory) && + entry.status().type() == fs::file_type::directory) { + if (!callback(entry)) { + callback_error = true; + break; + } + } + } + + if (callback_error || ec) { + // LOG_ERROR(Common_Filesystem, + // "Failed to visit all the directory entries of path={}, ec_message={}", + // PathToUTF8String(path), ec.message()); + return; + } + + //LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}", + // PathToUTF8String(path)); +} + +void IterateDirEntriesRecursively(const std::filesystem::path& path, + const DirEntryCallable& callback, DirEntryFilter filter) { + if (!ValidatePath(path)) { + // LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return; + } + + if (!Exists(path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist", + // PathToUTF8String(path)); + return; + } + + if (!IsDir(path)) { + // LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + // PathToUTF8String(path)); + return; + } + + bool callback_error = false; + + std::error_code ec; + + // TODO (Morph): Replace this with recursive_directory_iterator once it's fixed in MSVC. + for (const auto& entry : fs::directory_iterator(path, ec)) { + if (ec) { + break; + } + + if (True(filter & DirEntryFilter::File) && + entry.status().type() == fs::file_type::regular) { + if (!callback(entry)) { + callback_error = true; + break; + } + } + + if (True(filter & DirEntryFilter::Directory) && + entry.status().type() == fs::file_type::directory) { + if (!callback(entry)) { + callback_error = true; + break; + } + } + + // TODO (Morph): Remove this when MSVC fixes recursive_directory_iterator. + // recursive_directory_iterator throws an exception despite passing in a std::error_code. + if (entry.status().type() == fs::file_type::directory) { + IterateDirEntriesRecursively(entry.path(), callback, filter); + } + } + + if (callback_error || ec) { + //LOG_ERROR(Common_Filesystem, + // "Failed to visit all the directory entries of path={}, ec_message={}", + // PathToUTF8String(path), ec.message()); + return; + } + + //LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}", + // PathToUTF8String(path)); +} + +// Generic Filesystem Operations + +bool Exists(const fs::path& path) { + std::error_code ec; +#ifdef ANDROID + if (Android::IsContentUri(path)) { + return Android::Exists(path); + } else { + return fs::exists(path, ec); + } +#else + return fs::exists(path, ec); +#endif +} + +bool IsFile(const fs::path& path) { + std::error_code ec; +#ifdef ANDROID + if (Android::IsContentUri(path)) { + return !Android::IsDirectory(path); + } else { + return fs::is_regular_file(path, ec); + } +#else + return fs::is_regular_file(path, ec); +#endif +} + +bool IsDir(const fs::path& path) { + std::error_code ec; +#ifdef ANDROID + if (Android::IsContentUri(path)) { + return Android::IsDirectory(path); + } else { + return fs::is_directory(path, ec); + } +#else + return fs::is_directory(path, ec); +#endif +} + +fs::path GetCurrentDir() { + std::error_code ec; + + const auto current_path = fs::current_path(ec); + + if (ec) { + // LOG_ERROR(Common_Filesystem, "Failed to get the current path, ec_message={}", ec.message()); + return {}; + } + + return current_path; +} + +bool SetCurrentDir(const fs::path& path) { + std::error_code ec; + + fs::current_path(path, ec); + + if (ec) { + // LOG_ERROR(Common_Filesystem, "Failed to set the current path to path={}, ec_message={}", + // PathToUTF8String(path), ec.message()); + return false; + } + + return true; +} + +fs::file_type GetEntryType(const fs::path& path) { + std::error_code ec; + + const auto file_status = fs::status(path, ec); + + if (ec) { + // LOG_ERROR(Common_Filesystem, "Failed to retrieve the entry type of path={}, ec_message={}", + // PathToUTF8String(path), ec.message()); + return fs::file_type::not_found; + } + + return file_status.type(); +} + +#if 0 +u64 GetSize(const fs::path& path) { +#ifdef ANDROID + if (Android::IsContentUri(path)) { + return Android::GetSize(path); + } +#endif + + std::error_code ec; + + const auto file_size = fs::file_size(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return 0; + } + + return file_size; +} + +u64 GetFreeSpaceSize(const fs::path& path) { + std::error_code ec; + + const auto space_info = fs::space(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to retrieve the available free space of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return 0; + } + + return space_info.free; +} + +u64 GetTotalSpaceSize(const fs::path& path) { + std::error_code ec; + + const auto space_info = fs::space(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to retrieve the total capacity of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return 0; + } + + return space_info.capacity; +} +#endif +} // namespace Common::FS diff --git a/src/common/fs.h b/src/common/fs.h new file mode 100644 index 00000000..2c171436 --- /dev/null +++ b/src/common/fs.h @@ -0,0 +1,658 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include "io_file.h" + +namespace Common::FS { + +#define DECLARE_ENUM_FLAG_OPERATORS(type) \ + [[nodiscard]] constexpr type operator|(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) | static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator&(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) & static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator^(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) ^ static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) << static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) >> static_cast(b)); \ + } \ + constexpr type& operator|=(type& a, type b) noexcept { \ + a = a | b; \ + return a; \ + } \ + constexpr type& operator&=(type& a, type b) noexcept { \ + a = a & b; \ + return a; \ + } \ + constexpr type& operator^=(type& a, type b) noexcept { \ + a = a ^ b; \ + return a; \ + } \ + constexpr type& operator<<=(type& a, type b) noexcept { \ + a = a << b; \ + return a; \ + } \ + constexpr type& operator>>=(type& a, type b) noexcept { \ + a = a >> b; \ + return a; \ + } \ + [[nodiscard]] constexpr type operator~(type key) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(~static_cast(key)); \ + } \ + [[nodiscard]] constexpr bool True(type key) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(key) != 0; \ + } \ + [[nodiscard]] constexpr bool False(type key) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(key) == 0; \ + } + +class IOFile; + +enum class DirEntryFilter { + File = 1 << 0, + Directory = 1 << 1, + All = File | Directory, +}; +DECLARE_ENUM_FLAG_OPERATORS(DirEntryFilter); + +/** + * A callback function which takes in the path of a directory entry. + * + * @param path The path of a directory entry + * + * @returns A boolean value. + * Return true to indicate whether the callback is successful, false otherwise. + */ +using DirEntryCallable = std::function; + + +// File Operations + +/** + * Creates a new file at path with the specified size. + * + * Failures occur when: + * - Input path is not valid + * - The input path's parent directory does not exist + * - Filesystem object at path exists + * - Filesystem at path is read only + * + * @param path Filesystem path + * @param size File size + * + * @returns True if the file creation succeeds, false otherwise. + */ +[[nodiscard]] bool NewFile(const std::filesystem::path& path, u64 size = 0); + +#ifdef _WIN32 +template +[[nodiscard]] bool NewFile(const Path& path, u64 size = 0) { + if constexpr (IsChar) { + return NewFile(ToU8String(path), size); + } else { + return NewFile(std::filesystem::path{path}, size); + } +} +#endif + +/** + * Removes a file at path. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem object at path is not a regular file + * - Filesystem at path is read only + * + * @param path Filesystem path + * + * @returns True if file removal succeeds or file does not exist, false otherwise. + */ +bool RemoveFile(const std::filesystem::path& path); + +#ifdef _WIN32 +template +bool RemoveFile(const Path& path) { + if constexpr (IsChar) { + return RemoveFile(ToU8String(path)); + } else { + return RemoveFile(std::filesystem::path{path}); + } +} +#endif + +/** + * Renames a file from old_path to new_path. + * + * Failures occur when: + * - One or both input path(s) is not valid + * - Filesystem object at old_path does not exist + * - Filesystem object at old_path is not a regular file + * - Filesystem object at new_path exists + * - Filesystem at either path is read only + * + * @param old_path Old filesystem path + * @param new_path New filesystem path + * + * @returns True if file rename succeeds, false otherwise. + */ +[[nodiscard]] bool RenameFile(const std::filesystem::path& old_path, + const std::filesystem::path& new_path); + +#ifdef _WIN32 +template +[[nodiscard]] bool RenameFile(const Path1& old_path, const Path2& new_path) { + using ValueType1 = typename Path1::value_type; + using ValueType2 = typename Path2::value_type; + if constexpr (IsChar && IsChar) { + return RenameFile(ToU8String(old_path), ToU8String(new_path)); + } else if constexpr (IsChar && !IsChar) { + return RenameFile(ToU8String(old_path), new_path); + } else if constexpr (!IsChar && IsChar) { + return RenameFile(old_path, ToU8String(new_path)); + } else { + return RenameFile(std::filesystem::path{old_path}, std::filesystem::path{new_path}); + } +} +#endif + +/** + * Opens a file at path with the specified file access mode. + * This function behaves differently depending on the FileAccessMode. + * These behaviors are documented in each enum value of FileAccessMode. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem object at path exists and is not a regular file + * - The file is not open + * + * @param path Filesystem path + * @param mode File access mode + * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file + * @param flag (Windows only) File-share access flag, default is ShareReadOnly + * + * @returns A shared pointer to the opened file. Returns nullptr on failure. + */ +[[nodiscard]] std::shared_ptr FileOpen(const std::filesystem::path& path, + FileAccessMode mode, + FileType type = FileType::BinaryFile, + FileShareFlag flag = FileShareFlag::ShareReadOnly); + +#ifdef _WIN32 +template +[[nodiscard]] std::shared_ptr FileOpen(const Path& path, FileAccessMode mode, + FileType type = FileType::BinaryFile, + FileShareFlag flag = FileShareFlag::ShareReadOnly) { + if constexpr (IsChar) { + return FileOpen(ToU8String(path), mode, type, flag); + } else { + return FileOpen(std::filesystem::path{path}, mode, type, flag); + } +} +#endif + +// Directory Operations + +/** + * Creates a directory at path. + * Note that this function will *always* assume that the input path is a directory. For example, + * if the input path is /path/to/directory/file.txt, it will create a directory called "file.txt". + * If you intend to create the parent directory of a file, use CreateParentDir instead. + * + * Failures occur when: + * - Input path is not valid + * - The input path's parent directory does not exist + * - Filesystem at path is read only + * + * @param path Filesystem path + * + * @returns True if directory creation succeeds or directory already exists, false otherwise. + */ +[[nodiscard]] bool CreateDir(const std::filesystem::path& path); + +#ifdef _WIN32 +template +[[nodiscard]] bool CreateDir(const Path& path) { + if constexpr (IsChar) { + return CreateDir(ToU8String(path)); + } else { + return CreateDir(std::filesystem::path{path}); + } +} +#endif + +/** + * Recursively creates a directory at path. + * Note that this function will *always* assume that the input path is a directory. For example, + * if the input path is /path/to/directory/file.txt, it will create a directory called "file.txt". + * If you intend to create the parent directory of a file, use CreateParentDirs instead. + * Unlike CreateDir, this creates all of input path's parent directories if they do not exist. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem at path is read only + * + * @param path Filesystem path + * + * @returns True if directory creation succeeds or directory already exists, false otherwise. + */ +[[nodiscard]] bool CreateDirs(const std::filesystem::path& path); + +#ifdef _WIN32 +template +[[nodiscard]] bool CreateDirs(const Path& path) { + if constexpr (IsChar) { + return CreateDirs(ToU8String(path)); + } else { + return CreateDirs(std::filesystem::path{path}); + } +} +#endif + +/** + * Creates the parent directory of a given path. + * This function calls CreateDir(path.parent_path()), see CreateDir for more details. + * + * @param path Filesystem path + * + * @returns True if directory creation succeeds or directory already exists, false otherwise. + */ +[[nodiscard]] bool CreateParentDir(const std::filesystem::path& path); + +#ifdef _WIN32 +template +[[nodiscard]] bool CreateParentDir(const Path& path) { + if constexpr (IsChar) { + return CreateParentDir(ToU8String(path)); + } else { + return CreateParentDir(std::filesystem::path{path}); + } +} +#endif + +/** + * Recursively creates the parent directory of a given path. + * This function calls CreateDirs(path.parent_path()), see CreateDirs for more details. + * + * @param path Filesystem path + * + * @returns True if directory creation succeeds or directory already exists, false otherwise. + */ +[[nodiscard]] bool CreateParentDirs(const std::filesystem::path& path); + +#ifdef _WIN32 +template +[[nodiscard]] bool CreateParentDirs(const Path& path) { + if constexpr (IsChar) { + return CreateParentDirs(ToU8String(path)); + } else { + return CreateParentDirs(std::filesystem::path{path}); + } +} +#endif + +/** + * Removes a directory at path. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem object at path is not a directory + * - The given directory is not empty + * - Filesystem at path is read only + * + * @param path Filesystem path + * + * @returns True if directory removal succeeds or directory does not exist, false otherwise. + */ +bool RemoveDir(const std::filesystem::path& path); + +#ifdef _WIN32 +template +bool RemoveDir(const Path& path) { + if constexpr (IsChar) { + return RemoveDir(ToU8String(path)); + } else { + return RemoveDir(std::filesystem::path{path}); + } +} +#endif + +/** + * Removes all the contents within the given directory and removes the directory itself. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem object at path is not a directory + * - Filesystem at path is read only + * + * @param path Filesystem path + * + * @returns True if the directory and all of its contents are removed successfully, false otherwise. + */ +bool RemoveDirRecursively(const std::filesystem::path& path); + +#ifdef _WIN32 +template +bool RemoveDirRecursively(const Path& path) { + if constexpr (IsChar) { + return RemoveDirRecursively(ToU8String(path)); + } else { + return RemoveDirRecursively(std::filesystem::path{path}); + } +} +#endif + +/** + * Removes all the contents within the given directory without removing the directory itself. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem object at path is not a directory + * - Filesystem at path is read only + * + * @param path Filesystem path + * + * @returns True if all of the directory's contents are removed successfully, false otherwise. + */ +bool RemoveDirContentsRecursively(const std::filesystem::path& path); + +#ifdef _WIN32 +template +bool RemoveDirContentsRecursively(const Path& path) { + if constexpr (IsChar) { + return RemoveDirContentsRecursively(ToU8String(path)); + } else { + return RemoveDirContentsRecursively(std::filesystem::path{path}); + } +} +#endif + +/** + * Renames a directory from old_path to new_path. + * + * Failures occur when: + * - One or both input path(s) is not valid + * - Filesystem object at old_path does not exist + * - Filesystem object at old_path is not a directory + * - Filesystem object at new_path exists + * - Filesystem at either path is read only + * + * @param old_path Old filesystem path + * @param new_path New filesystem path + * + * @returns True if directory rename succeeds, false otherwise. + */ +[[nodiscard]] bool RenameDir(const std::filesystem::path& old_path, + const std::filesystem::path& new_path); + +#ifdef _WIN32 +template +[[nodiscard]] bool RenameDir(const Path1& old_path, const Path2& new_path) { + using ValueType1 = typename Path1::value_type; + using ValueType2 = typename Path2::value_type; + if constexpr (IsChar && IsChar) { + return RenameDir(ToU8String(old_path), ToU8String(new_path)); + } else if constexpr (IsChar && !IsChar) { + return RenameDir(ToU8String(old_path), new_path); + } else if constexpr (!IsChar && IsChar) { + return RenameDir(old_path, ToU8String(new_path)); + } else { + return RenameDir(std::filesystem::path{old_path}, std::filesystem::path{new_path}); + } +} +#endif + +/** + * Iterates over the directory entries of a given directory. + * This does not iterate over the sub-directories of the given directory. + * The DirEntryCallable callback is called for each visited directory entry. + * A filter can be set to control which directory entries are visited based on their type. + * By default, both files and directories are visited. + * If the callback returns false or there is an error, the iteration is immediately halted. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem object at path is not a directory + * + * @param path Filesystem path + * @param callback Callback to be called for each visited directory entry + * @param filter Directory entry type filter + */ +void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback, + DirEntryFilter filter = DirEntryFilter::All); + +#ifdef _WIN32 +template +void IterateDirEntries(const Path& path, const DirEntryCallable& callback, + DirEntryFilter filter = DirEntryFilter::All) { + if constexpr (IsChar) { + IterateDirEntries(ToU8String(path), callback, filter); + } else { + IterateDirEntries(std::filesystem::path{path}, callback, filter); + } +} +#endif + +/** + * Iterates over the directory entries of a given directory and its sub-directories. + * The DirEntryCallable callback is called for each visited directory entry. + * A filter can be set to control which directory entries are visited based on their type. + * By default, both files and directories are visited. + * If the callback returns false or there is an error, the iteration is immediately halted. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem object at path does not exist + * - Filesystem object at path is not a directory + * + * @param path Filesystem path + * @param callback Callback to be called for each visited directory entry + * @param filter Directory entry type filter + */ +void IterateDirEntriesRecursively(const std::filesystem::path& path, + const DirEntryCallable& callback, + DirEntryFilter filter = DirEntryFilter::All); + +#ifdef _WIN32 +template +void IterateDirEntriesRecursively(const Path& path, const DirEntryCallable& callback, + DirEntryFilter filter = DirEntryFilter::All) { + if constexpr (IsChar) { + IterateDirEntriesRecursively(ToU8String(path), callback, filter); + } else { + IterateDirEntriesRecursively(std::filesystem::path{path}, callback, filter); + } +} +#endif + +// Generic Filesystem Operations + +/** + * Returns whether a filesystem object at path exists. + * + * @param path Filesystem path + * + * @returns True if a filesystem object at path exists, false otherwise. + */ +[[nodiscard]] bool Exists(const std::filesystem::path& path); + +#ifdef _WIN32 +template +[[nodiscard]] bool Exists(const Path& path) { + if constexpr (IsChar) { + return Exists(ToU8String(path)); + } else { + return Exists(std::filesystem::path{path}); + } +} +#endif + +/** + * Returns whether a filesystem object at path is a regular file. + * A regular file is a file that stores text or binary data. + * It is not a directory, symlink, FIFO, socket, block device, or character device. + * + * @param path Filesystem path + * + * @returns True if a filesystem object at path is a regular file, false otherwise. + */ +[[nodiscard]] bool IsFile(const std::filesystem::path& path); + +#ifdef _WIN32 +template +[[nodiscard]] bool IsFile(const Path& path) { + if constexpr (IsChar) { + return IsFile(ToU8String(path)); + } else { + return IsFile(std::filesystem::path{path}); + } +} +#endif + +/** + * Returns whether a filesystem object at path is a directory. + * + * @param path Filesystem path + * + * @returns True if a filesystem object at path is a directory, false otherwise. + */ +[[nodiscard]] bool IsDir(const std::filesystem::path& path); + +#ifdef _WIN32 +template +[[nodiscard]] bool IsDir(const Path& path) { + if constexpr (IsChar) { + return IsDir(ToU8String(path)); + } else { + return IsDir(std::filesystem::path{path}); + } +} +#endif + +/** + * Gets the current working directory. + * + * @returns The current working directory. Returns an empty path on failure. + */ +[[nodiscard]] std::filesystem::path GetCurrentDir(); + +/** + * Sets the current working directory to path. + * + * @returns True if the current working directory is successfully set, false otherwise. + */ +[[nodiscard]] bool SetCurrentDir(const std::filesystem::path& path); + +#ifdef _WIN32 +template +[[nodiscard]] bool SetCurrentDir(const Path& path) { + if constexpr (IsChar) { + return SetCurrentDir(ToU8String(path)); + } else { + return SetCurrentDir(std::filesystem::path{path}); + } +} +#endif + +/** + * Gets the entry type of the filesystem object at path. + * + * @param path Filesystem path + * + * @returns The entry type of the filesystem object. Returns file_type::not_found on failure. + */ +[[nodiscard]] std::filesystem::file_type GetEntryType(const std::filesystem::path& path); + +#ifdef _WIN32 +template +[[nodiscard]] std::filesystem::file_type GetEntryType(const Path& path) { + if constexpr (IsChar) { + return GetEntryType(ToU8String(path)); + } else { + return GetEntryType(std::filesystem::path{path}); + } +} +#endif + +#if 0 +/** + * Gets the size of the filesystem object at path. + * + * @param path Filesystem path + * + * @returns The size in bytes of the filesystem object. Returns 0 on failure. + */ +[[nodiscard]] u64 GetSize(const std::filesystem::path& path); + +#ifdef _WIN32 +template +[[nodiscard]] u64 GetSize(const Path& path) { + if constexpr (IsChar) { + return GetSize(ToU8String(path)); + } else { + return GetSize(std::filesystem::path{path}); + } +} +#endif + + +/** + * Gets the free space size of the filesystem at path. + * + * @param path Filesystem path + * + * @returns The free space size in bytes of the filesystem at path. Returns 0 on failure. + */ +[[nodiscard]] u64 GetFreeSpaceSize(const std::filesystem::path& path); + +#ifdef _WIN32 +template +[[nodiscard]] u64 GetFreeSpaceSize(const Path& path) { + if constexpr (IsChar) { + return GetFreeSpaceSize(ToU8String(path)); + } else { + return GetFreeSpaceSize(std::filesystem::path{path}); + } +} +#endif + +/** + * Gets the total capacity of the filesystem at path. + * + * @param path Filesystem path + * + * @returns The total capacity in bytes of the filesystem at path. Returns 0 on failure. + */ +[[nodiscard]] u64 GetTotalSpaceSize(const std::filesystem::path& path); + +#ifdef _WIN32 +template +[[nodiscard]] u64 GetTotalSpaceSize(const Path& path) { + if constexpr (IsChar) { + return GetTotalSpaceSize(ToU8String(path)); + } else { + return GetTotalSpaceSize(std::filesystem::path{path}); + } +} +#endif + +#endif + +} // namespace Common::FS