mirror of https://git.suyu.dev/suyu/suyu
common: fs: Rework the Common Filesystem interface to make use of std::filesystem (#6270)
* common: fs: fs_types: Create filesystem types Contains various filesystem types used by the Common::FS library * common: fs: fs_util: Add std::string to std::u8string conversion utility * common: fs: path_util: Add utlity functions for paths Contains various utility functions for getting or manipulating filesystem paths used by the Common::FS library * common: fs: file: Rewrite the IOFile implementation * common: fs: Reimplement Common::FS library using std::filesystem * common: fs: fs_paths: Add fs_paths to replace common_paths * common: fs: path_util: Add the rest of the path functions * common: Remove the previous Common::FS implementation * general: Remove unused fs includes * string_util: Remove unused function and include * nvidia_flags: Migrate to the new Common::FS library * settings: Migrate to the new Common::FS library * logging: backend: Migrate to the new Common::FS library * core: Migrate to the new Common::FS library * perf_stats: Migrate to the new Common::FS library * reporter: Migrate to the new Common::FS library * telemetry_session: Migrate to the new Common::FS library * key_manager: Migrate to the new Common::FS library * bis_factory: Migrate to the new Common::FS library * registered_cache: Migrate to the new Common::FS library * xts_archive: Migrate to the new Common::FS library * service: acc: Migrate to the new Common::FS library * applets/profile: Migrate to the new Common::FS library * applets/web: Migrate to the new Common::FS library * service: filesystem: Migrate to the new Common::FS library * loader: Migrate to the new Common::FS library * gl_shader_disk_cache: Migrate to the new Common::FS library * nsight_aftermath_tracker: Migrate to the new Common::FS library * vulkan_library: Migrate to the new Common::FS library * configure_debug: Migrate to the new Common::FS library * game_list_worker: Migrate to the new Common::FS library * config: Migrate to the new Common::FS library * configure_filesystem: Migrate to the new Common::FS library * configure_per_game_addons: Migrate to the new Common::FS library * configure_profile_manager: Migrate to the new Common::FS library * configure_ui: Migrate to the new Common::FS library * input_profiles: Migrate to the new Common::FS library * yuzu_cmd: config: Migrate to the new Common::FS library * yuzu_cmd: Migrate to the new Common::FS library * vfs_real: Migrate to the new Common::FS library * vfs: Migrate to the new Common::FS library * vfs_libzip: Migrate to the new Common::FS library * service: bcat: Migrate to the new Common::FS library * yuzu: main: Migrate to the new Common::FS library * vfs_real: Delete the contents of an existing file in CreateFile Current usages of CreateFile expect to delete the contents of an existing file, retain this behavior for now. * input_profiles: Don't iterate the input profile dir if it does not exist Silences an error produced in the log if the directory does not exist. * game_list_worker: Skip parsing file if the returned VfsFile is nullptr Prevents crashes in GetLoader when the virtual file is nullptr * common: fs: Validate paths for path length * service: filesystem: Open the mod load directory as read onlymerge-requests/60/head
parent
08a5cf0b5b
commit
065867e2c2
@ -1,52 +0,0 @@
|
||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Directory separators, do we need this?
|
||||
#define DIR_SEP "/"
|
||||
#define DIR_SEP_CHR '/'
|
||||
|
||||
#ifndef MAX_PATH
|
||||
#define MAX_PATH 260
|
||||
#endif
|
||||
|
||||
// The user data dir
|
||||
#define ROOT_DIR "."
|
||||
#define USERDATA_DIR "user"
|
||||
#ifdef USER_DIR
|
||||
#define EMU_DATA_DIR USER_DIR
|
||||
#else
|
||||
#define EMU_DATA_DIR "yuzu"
|
||||
#endif
|
||||
|
||||
// Dirs in both User and Sys
|
||||
#define EUR_DIR "EUR"
|
||||
#define USA_DIR "USA"
|
||||
#define JAP_DIR "JAP"
|
||||
|
||||
// Subdirs in the User dir returned by GetUserPath(UserPath::UserDir)
|
||||
#define CONFIG_DIR "config"
|
||||
#define CACHE_DIR "cache"
|
||||
#define SDMC_DIR "sdmc"
|
||||
#define NAND_DIR "nand"
|
||||
#define SYSDATA_DIR "sysdata"
|
||||
#define KEYS_DIR "keys"
|
||||
#define LOAD_DIR "load"
|
||||
#define DUMP_DIR "dump"
|
||||
#define SCREENSHOTS_DIR "screenshots"
|
||||
#define SHADER_DIR "shader"
|
||||
#define LOG_DIR "log"
|
||||
|
||||
// Filenames
|
||||
// Files in the directory returned by GetUserPath(UserPath::ConfigDir)
|
||||
#define EMU_CONFIG "emu.ini"
|
||||
#define DEBUGGER_CONFIG "debugger.ini"
|
||||
#define LOGGER_CONFIG "logger.ini"
|
||||
// Files in the directory returned by GetUserPath(UserPath::LogDir)
|
||||
#define LOG_FILE "yuzu_log.txt"
|
||||
|
||||
// Sys files
|
||||
#define SHARED_FONT "shared_font.bin"
|
||||
#define AES_KEYS "aes_keys.txt"
|
File diff suppressed because it is too large
Load Diff
@ -1,298 +0,0 @@
|
||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#ifdef _MSC_VER
|
||||
#include "common/string_util.h"
|
||||
#endif
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
// User paths for GetUserPath
|
||||
enum class UserPath {
|
||||
CacheDir,
|
||||
ConfigDir,
|
||||
KeysDir,
|
||||
LogDir,
|
||||
NANDDir,
|
||||
RootDir,
|
||||
SDMCDir,
|
||||
LoadDir,
|
||||
DumpDir,
|
||||
ScreenshotsDir,
|
||||
ShaderDir,
|
||||
SysDataDir,
|
||||
UserDir,
|
||||
};
|
||||
|
||||
// FileSystem tree node/
|
||||
struct FSTEntry {
|
||||
bool isDirectory;
|
||||
u64 size; // file length or number of entries from children
|
||||
std::string physicalName; // name on disk
|
||||
std::string virtualName; // name in FST names table
|
||||
std::vector<FSTEntry> children;
|
||||
};
|
||||
|
||||
// Returns true if file filename exists
|
||||
[[nodiscard]] bool Exists(const std::string& filename);
|
||||
|
||||
// Returns true if filename is a directory
|
||||
[[nodiscard]] bool IsDirectory(const std::string& filename);
|
||||
|
||||
// Returns the size of filename (64bit)
|
||||
[[nodiscard]] u64 GetSize(const std::string& filename);
|
||||
|
||||
// Overloaded GetSize, accepts file descriptor
|
||||
[[nodiscard]] u64 GetSize(int fd);
|
||||
|
||||
// Overloaded GetSize, accepts FILE*
|
||||
[[nodiscard]] u64 GetSize(FILE* f);
|
||||
|
||||
// Returns true if successful, or path already exists.
|
||||
bool CreateDir(const std::string& filename);
|
||||
|
||||
// Creates the full path of fullPath returns true on success
|
||||
bool CreateFullPath(const std::string& fullPath);
|
||||
|
||||
// Deletes a given filename, return true on success
|
||||
// Doesn't supports deleting a directory
|
||||
bool Delete(const std::string& filename);
|
||||
|
||||
// Deletes a directory filename, returns true on success
|
||||
bool DeleteDir(const std::string& filename);
|
||||
|
||||
// renames file srcFilename to destFilename, returns true on success
|
||||
bool Rename(const std::string& srcFilename, const std::string& destFilename);
|
||||
|
||||
// copies file srcFilename to destFilename, returns true on success
|
||||
bool Copy(const std::string& srcFilename, const std::string& destFilename);
|
||||
|
||||
// creates an empty file filename, returns true on success
|
||||
bool CreateEmptyFile(const std::string& filename);
|
||||
|
||||
/**
|
||||
* @param num_entries_out to be assigned by the callable with the number of iterated directory
|
||||
* entries, never null
|
||||
* @param directory the path to the enclosing directory
|
||||
* @param virtual_name the entry name, without any preceding directory info
|
||||
* @return whether handling the entry succeeded
|
||||
*/
|
||||
using DirectoryEntryCallable = std::function<bool(
|
||||
u64* num_entries_out, const std::string& directory, const std::string& virtual_name)>;
|
||||
|
||||
/**
|
||||
* Scans a directory, calling the callback for each file/directory contained within.
|
||||
* If the callback returns failure, scanning halts and this function returns failure as well
|
||||
* @param num_entries_out assigned by the function with the number of iterated directory entries,
|
||||
* can be null
|
||||
* @param directory the directory to scan
|
||||
* @param callback The callback which will be called for each entry
|
||||
* @return whether scanning the directory succeeded
|
||||
*/
|
||||
bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
|
||||
DirectoryEntryCallable callback);
|
||||
|
||||
/**
|
||||
* Scans the directory tree, storing the results.
|
||||
* @param directory the parent directory to start scanning from
|
||||
* @param parent_entry FSTEntry where the filesystem tree results will be stored.
|
||||
* @param recursion Number of children directories to read before giving up.
|
||||
* @return the total number of files/directories found
|
||||
*/
|
||||
u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
|
||||
unsigned int recursion = 0);
|
||||
|
||||
// deletes the given directory and anything under it. Returns true on success.
|
||||
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256);
|
||||
|
||||
// Returns the current directory
|
||||
[[nodiscard]] std::optional<std::string> GetCurrentDir();
|
||||
|
||||
// Create directory and copy contents (does not overwrite existing files)
|
||||
void CopyDir(const std::string& source_path, const std::string& dest_path);
|
||||
|
||||
// Set the current directory to given directory
|
||||
bool SetCurrentDir(const std::string& directory);
|
||||
|
||||
// Returns a pointer to a string with a yuzu data dir in the user's home
|
||||
// directory. To be used in "multi-user" mode (that is, installed).
|
||||
const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
|
||||
|
||||
[[nodiscard]] std::string GetHactoolConfigurationPath();
|
||||
|
||||
[[nodiscard]] std::string GetNANDRegistrationDir(bool system = false);
|
||||
|
||||
// Returns the path to where the sys file are
|
||||
[[nodiscard]] std::string GetSysDirectory();
|
||||
|
||||
#ifdef __APPLE__
|
||||
[[nodiscard]] std::string GetBundleDirectory();
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
[[nodiscard]] const std::string& GetExeDirectory();
|
||||
[[nodiscard]] std::string AppDataRoamingDirectory();
|
||||
#endif
|
||||
|
||||
std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str);
|
||||
|
||||
std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str);
|
||||
|
||||
/**
|
||||
* Splits the filename into 8.3 format
|
||||
* Loosely implemented following https://en.wikipedia.org/wiki/8.3_filename
|
||||
* @param filename The normal filename to use
|
||||
* @param short_name A 9-char array in which the short name will be written
|
||||
* @param extension A 4-char array in which the extension will be written
|
||||
*/
|
||||
void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name,
|
||||
std::array<char, 4>& extension);
|
||||
|
||||
// Splits the path on '/' or '\' and put the components into a vector
|
||||
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
|
||||
[[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename);
|
||||
|
||||
// Gets all of the text up to the last '/' or '\' in the path.
|
||||
[[nodiscard]] std::string_view GetParentPath(std::string_view path);
|
||||
|
||||
// Gets all of the text after the first '/' or '\' in the path.
|
||||
[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path);
|
||||
|
||||
// Gets the filename of the path
|
||||
[[nodiscard]] std::string_view GetFilename(std::string_view path);
|
||||
|
||||
// Gets the extension of the filename
|
||||
[[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name);
|
||||
|
||||
// Removes the final '/' or '\' if one exists
|
||||
[[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path);
|
||||
|
||||
// Creates a new vector containing indices [first, last) from the original.
|
||||
template <typename T>
|
||||
[[nodiscard]] std::vector<T> SliceVector(const std::vector<T>& vector, std::size_t first,
|
||||
std::size_t last) {
|
||||
if (first >= last) {
|
||||
return {};
|
||||
}
|
||||
last = std::min<std::size_t>(last, vector.size());
|
||||
return std::vector<T>(vector.begin() + first, vector.begin() + first + last);
|
||||
}
|
||||
|
||||
enum class DirectorySeparator {
|
||||
ForwardSlash,
|
||||
BackwardSlash,
|
||||
PlatformDefault,
|
||||
};
|
||||
|
||||
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
|
||||
// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
|
||||
[[nodiscard]] std::string SanitizePath(
|
||||
std::string_view path,
|
||||
DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
|
||||
|
||||
// To deal with Windows being dumb at Unicode
|
||||
template <typename T>
|
||||
void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
|
||||
#ifdef _MSC_VER
|
||||
fstream.open(Common::UTF8ToUTF16W(filename), openmode);
|
||||
#else
|
||||
fstream.open(filename, openmode);
|
||||
#endif
|
||||
}
|
||||
|
||||
// simple wrapper for cstdlib file functions to
|
||||
// hopefully will make error checking easier
|
||||
// and make forgetting an fclose() harder
|
||||
class IOFile : public NonCopyable {
|
||||
public:
|
||||
IOFile();
|
||||
// flags is used for windows specific file open mode flags, which
|
||||
// allows yuzu to open the logs in shared write mode, so that the file
|
||||
// isn't considered "locked" while yuzu is open and people can open the log file and view it
|
||||
IOFile(const std::string& filename, const char openmode[], int flags = 0);
|
||||
|
||||
~IOFile();
|
||||
|
||||
IOFile(IOFile&& other) noexcept;
|
||||
IOFile& operator=(IOFile&& other) noexcept;
|
||||
|
||||
void Swap(IOFile& other) noexcept;
|
||||
|
||||
bool Open(const std::string& filename, const char openmode[], int flags = 0);
|
||||
bool Close();
|
||||
|
||||
template <typename T>
|
||||
std::size_t ReadArray(T* data, std::size_t length) const {
|
||||
static_assert(std::is_trivially_copyable_v<T>,
|
||||
"Given array does not consist of trivially copyable objects");
|
||||
|
||||
return ReadImpl(data, length, sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::size_t WriteArray(const T* data, std::size_t length) {
|
||||
static_assert(std::is_trivially_copyable_v<T>,
|
||||
"Given array does not consist of trivially copyable objects");
|
||||
|
||||
return WriteImpl(data, length, sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::size_t ReadBytes(T* data, std::size_t length) const {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
||||
return ReadArray(reinterpret_cast<char*>(data), length);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::size_t WriteBytes(const T* data, std::size_t length) {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
||||
return WriteArray(reinterpret_cast<const char*>(data), length);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::size_t WriteObject(const T& object) {
|
||||
static_assert(!std::is_pointer_v<T>, "WriteObject arguments must not be a pointer");
|
||||
return WriteArray(&object, 1);
|
||||
}
|
||||
|
||||
std::size_t WriteString(std::string_view str) {
|
||||
return WriteArray(str.data(), str.length());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsOpen() const {
|
||||
return nullptr != m_file;
|
||||
}
|
||||
|
||||
bool Seek(s64 off, int origin) const;
|
||||
[[nodiscard]] u64 Tell() const;
|
||||
[[nodiscard]] u64 GetSize() const;
|
||||
bool Resize(u64 size);
|
||||
bool Flush();
|
||||
|
||||
// clear error state
|
||||
void Clear() {
|
||||
std::clearerr(m_file);
|
||||
}
|
||||
|
||||
private:
|
||||
std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size) const;
|
||||
std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size);
|
||||
|
||||
std::FILE* m_file = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Common::FS
|
@ -0,0 +1,392 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <share.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define fileno _fileno
|
||||
#define fseeko _fseeki64
|
||||
#define ftello _ftelli64
|
||||
#endif
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
/**
|
||||
* Converts the file access mode and file type enums to a file access mode wide string.
|
||||
*
|
||||
* @param mode File access mode
|
||||
* @param type File type
|
||||
*
|
||||
* @returns A pointer to a wide string representing the file access mode.
|
||||
*/
|
||||
[[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileType type) {
|
||||
switch (type) {
|
||||
case FileType::BinaryFile:
|
||||
switch (mode) {
|
||||
case FileAccessMode::Read:
|
||||
return L"rb";
|
||||
case FileAccessMode::Write:
|
||||
return L"wb";
|
||||
case FileAccessMode::Append:
|
||||
return L"ab";
|
||||
case FileAccessMode::ReadWrite:
|
||||
return L"r+b";
|
||||
case FileAccessMode::ReadAppend:
|
||||
return L"a+b";
|
||||
}
|
||||
break;
|
||||
case FileType::TextFile:
|
||||
switch (mode) {
|
||||
case FileAccessMode::Read:
|
||||
return L"r";
|
||||
case FileAccessMode::Write:
|
||||
return L"w";
|
||||
case FileAccessMode::Append:
|
||||
return L"a";
|
||||
case FileAccessMode::ReadWrite:
|
||||
return L"r+";
|
||||
case FileAccessMode::ReadAppend:
|
||||
return L"a+";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return L"";
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the file-share access flag enum to a Windows defined file-share access flag.
|
||||
*
|
||||
* @param flag File-share access flag
|
||||
*
|
||||
* @returns Windows defined file-share access flag.
|
||||
*/
|
||||
[[nodiscard]] constexpr int ToWindowsFileShareFlag(FileShareFlag flag) {
|
||||
switch (flag) {
|
||||
case FileShareFlag::ShareNone:
|
||||
default:
|
||||
return _SH_DENYRW;
|
||||
case FileShareFlag::ShareReadOnly:
|
||||
return _SH_DENYWR;
|
||||
case FileShareFlag::ShareWriteOnly:
|
||||
return _SH_DENYRD;
|
||||
case FileShareFlag::ShareReadWrite:
|
||||
return _SH_DENYNO;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
* Converts the file access mode and file type enums to a file access mode string.
|
||||
*
|
||||
* @param mode File access mode
|
||||
* @param type File type
|
||||
*
|
||||
* @returns A pointer to a string representing the file access mode.
|
||||
*/
|
||||
[[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileType type) {
|
||||
switch (type) {
|
||||
case FileType::BinaryFile:
|
||||
switch (mode) {
|
||||
case FileAccessMode::Read:
|
||||
return "rb";
|
||||
case FileAccessMode::Write:
|
||||
return "wb";
|
||||
case FileAccessMode::Append:
|
||||
return "ab";
|
||||
case FileAccessMode::ReadWrite:
|
||||
return "r+b";
|
||||
case FileAccessMode::ReadAppend:
|
||||
return "a+b";
|
||||
}
|
||||
break;
|
||||
case FileType::TextFile:
|
||||
switch (mode) {
|
||||
case FileAccessMode::Read:
|
||||
return "r";
|
||||
case FileAccessMode::Write:
|
||||
return "w";
|
||||
case FileAccessMode::Append:
|
||||
return "a";
|
||||
case FileAccessMode::ReadWrite:
|
||||
return "r+";
|
||||
case FileAccessMode::ReadAppend:
|
||||
return "a+";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Converts the seek origin enum to a seek origin integer.
|
||||
*
|
||||
* @param origin Seek origin
|
||||
*
|
||||
* @returns Seek origin integer.
|
||||
*/
|
||||
[[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) {
|
||||
switch (origin) {
|
||||
case SeekOrigin::SetOrigin:
|
||||
default:
|
||||
return SEEK_SET;
|
||||
case SeekOrigin::CurrentPosition:
|
||||
return SEEK_CUR;
|
||||
case SeekOrigin::End:
|
||||
return SEEK_END;
|
||||
}
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
std::string ReadStringFromFile(const std::filesystem::path& path, FileType type) {
|
||||
if (!IsFile(path)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
IOFile io_file{path, FileAccessMode::Read, type};
|
||||
|
||||
return io_file.ReadString(io_file.GetSize());
|
||||
}
|
||||
|
||||
size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
|
||||
std::string_view string) {
|
||||
if (!IsFile(path)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
IOFile io_file{path, FileAccessMode::Write, type};
|
||||
|
||||
return io_file.WriteString(string);
|
||||
}
|
||||
|
||||
size_t AppendStringToFile(const std::filesystem::path& path, FileType type,
|
||||
std::string_view string) {
|
||||
if (!Exists(path)) {
|
||||
return WriteStringToFile(path, type, string);
|
||||
}
|
||||
|
||||
if (!IsFile(path)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
IOFile io_file{path, FileAccessMode::Append, type};
|
||||
|
||||
return io_file.WriteString(string);
|
||||
}
|
||||
|
||||
IOFile::IOFile() = default;
|
||||
|
||||
IOFile::IOFile(const std::string& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
|
||||
Open(path, mode, type, flag);
|
||||
}
|
||||
|
||||
IOFile::IOFile(std::string_view path, FileAccessMode mode, FileType type, FileShareFlag flag) {
|
||||
Open(path, mode, type, flag);
|
||||
}
|
||||
|
||||
IOFile::IOFile(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
|
||||
Open(path, mode, type, flag);
|
||||
}
|
||||
|
||||
IOFile::~IOFile() {
|
||||
Close();
|
||||
}
|
||||
|
||||
IOFile::IOFile(IOFile&& other) noexcept {
|
||||
std::swap(file_path, other.file_path);
|
||||
std::swap(file_access_mode, other.file_access_mode);
|
||||
std::swap(file_type, other.file_type);
|
||||
std::swap(file, other.file);
|
||||
}
|
||||
|
||||
IOFile& IOFile::operator=(IOFile&& other) noexcept {
|
||||
std::swap(file_path, other.file_path);
|
||||
std::swap(file_access_mode, other.file_access_mode);
|
||||
std::swap(file_type, other.file_type);
|
||||
std::swap(file, other.file);
|
||||
return *this;
|
||||
}
|
||||
|
||||
fs::path IOFile::GetPath() const {
|
||||
return file_path;
|
||||
}
|
||||
|
||||
FileAccessMode IOFile::GetAccessMode() const {
|
||||
return file_access_mode;
|
||||
}
|
||||
|
||||
FileType IOFile::GetType() const {
|
||||
return file_type;
|
||||
}
|
||||
|
||||
void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
|
||||
Close();
|
||||
|
||||
file_path = path;
|
||||
file_access_mode = mode;
|
||||
file_type = type;
|
||||
|
||||
errno = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (flag != FileShareFlag::ShareNone) {
|
||||
file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
|
||||
} else {
|
||||
_wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
|
||||
}
|
||||
#else
|
||||
file = std::fopen(path.c_str(), AccessModeToStr(mode, type));
|
||||
#endif
|
||||
|
||||
if (!IsOpen()) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
}
|
||||
}
|
||||
|
||||
void IOFile::Close() {
|
||||
if (!IsOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
|
||||
const auto close_result = std::fclose(file) == 0;
|
||||
|
||||
if (!close_result) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to close the file at path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
}
|
||||
|
||||
file = nullptr;
|
||||
}
|
||||
|
||||
bool IOFile::IsOpen() const {
|
||||
return file != nullptr;
|
||||
}
|
||||
|
||||
std::string IOFile::ReadString(size_t length) const {
|
||||
std::vector<char> string_buffer(length);
|
||||
|
||||
const auto chars_read = ReadSpan<char>(string_buffer);
|
||||
const auto string_size = chars_read != length ? chars_read : length;
|
||||
|
||||
return std::string{string_buffer.data(), string_size};
|
||||
}
|
||||
|
||||
size_t IOFile::WriteString(std::span<const char> string) const {
|
||||
return WriteSpan(string);
|
||||
}
|
||||
|
||||
bool IOFile::Flush() const {
|
||||
if (!IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
|
||||
const auto flush_result = std::fflush(file) == 0;
|
||||
|
||||
if (!flush_result) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to flush the file at path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
}
|
||||
|
||||
return flush_result;
|
||||
}
|
||||
|
||||
bool IOFile::SetSize(u64 size) const {
|
||||
if (!IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
const auto set_size_result = _chsize_s(fileno(file), static_cast<s64>(size)) == 0;
|
||||
#else
|
||||
const auto set_size_result = ftruncate(fileno(file), static_cast<s64>(size)) == 0;
|
||||
#endif
|
||||
|
||||
if (!set_size_result) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}",
|
||||
PathToUTF8String(file_path), size, ec.message());
|
||||
}
|
||||
|
||||
return set_size_result;
|
||||
}
|
||||
|
||||
u64 IOFile::GetSize() const {
|
||||
if (!IsOpen()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
|
||||
const auto file_size = fs::file_size(file_path, ec);
|
||||
|
||||
if (ec) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
return 0;
|
||||
}
|
||||
|
||||
return file_size;
|
||||
}
|
||||
|
||||
bool IOFile::Seek(s64 offset, SeekOrigin origin) const {
|
||||
if (!IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
|
||||
const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0;
|
||||
|
||||
if (!seek_result) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem,
|
||||
"Failed to seek the file at path={}, offset={}, origin={}, ec_message={}",
|
||||
PathToUTF8String(file_path), offset, origin, ec.message());
|
||||
}
|
||||
|
||||
return seek_result;
|
||||
}
|
||||
|
||||
s64 IOFile::Tell() const {
|
||||
if (!IsOpen()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
|
||||
return ftello(file);
|
||||
}
|
||||
|
||||
} // namespace Common::FS
|
@ -0,0 +1,450 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "common/concepts.h"
|
||||
#include "common/fs/fs_types.h"
|
||||
#include "common/fs/fs_util.h"
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
enum class SeekOrigin {
|
||||
SetOrigin, // Seeks from the start of the file.
|
||||
CurrentPosition, // Seeks from the current file pointer position.
|
||||
End, // Seeks from the end of the file.
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a file stream at path with the specified open mode.
|
||||
*
|
||||
* @param file_stream Reference to file stream
|
||||
* @param path Filesystem path
|
||||
* @param open_mode File stream open mode
|
||||
*/
|
||||
template <typename FileStream>
|
||||
void OpenFileStream(FileStream& file_stream, const std::filesystem::path& path,
|
||||
std::ios_base::openmode open_mode) {
|
||||
file_stream.open(path, open_mode);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename FileStream, typename Path>
|
||||
void OpenFileStream(FileStream& file_stream, const Path& path, std::ios_base::openmode open_mode) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
file_stream.open(ToU8String(path), open_mode);
|
||||
} else {
|
||||
file_stream.open(std::filesystem::path{path}, open_mode);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Reads an entire file at path and returns a string of the contents read from the file.
|
||||
* If the filesystem object at path is not a file, this function returns an empty string.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param type File type
|
||||
*
|
||||
* @returns A string of the contents read from the file.
|
||||
*/
|
||||
[[nodiscard]] std::string ReadStringFromFile(const std::filesystem::path& path, FileType type);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] std::string ReadStringFromFile(const Path& path, FileType type) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
return ReadStringFromFile(ToU8String(path), type);
|
||||
} else {
|
||||
return ReadStringFromFile(std::filesystem::path{path}, type);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Writes a string to a file at path and returns the number of characters successfully written.
|
||||
* If an file already exists at path, its contents will be erased.
|
||||
* If the filesystem object at path is not a file, this function returns 0.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param type File type
|
||||
*
|
||||
* @returns Number of characters successfully written.
|
||||
*/
|
||||
[[nodiscard]] size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
|
||||
std::string_view string);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] size_t WriteStringToFile(const Path& path, FileType type, std::string_view string) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
return WriteStringToFile(ToU8String(path), type, string);
|
||||
} else {
|
||||
return WriteStringToFile(std::filesystem::path{path}, type, string);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Appends a string to a file at path and returns the number of characters successfully written.
|
||||
* If a file does not exist at path, WriteStringToFile is called instead.
|
||||
* If the filesystem object at path is not a file, this function returns 0.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param type File type
|
||||
*
|
||||
* @returns Number of characters successfully written.
|
||||
*/
|
||||
[[nodiscard]] size_t AppendStringToFile(const std::filesystem::path& path, FileType type,
|
||||
std::string_view string);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] size_t AppendStringToFile(const Path& path, FileType type, std::string_view string) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
return AppendStringToFile(ToU8String(path), type, string);
|
||||
} else {
|
||||
return AppendStringToFile(std::filesystem::path{path}, type, string);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
class IOFile final : NonCopyable {
|
||||
public:
|
||||
IOFile();
|
||||
|
||||
explicit IOFile(const std::string& path, FileAccessMode mode,
|
||||
FileType type = FileType::BinaryFile,
|
||||
FileShareFlag flag = FileShareFlag::ShareReadOnly);
|
||||
|
||||
explicit IOFile(std::string_view path, FileAccessMode mode,
|
||||
FileType type = FileType::BinaryFile,
|
||||
FileShareFlag flag = FileShareFlag::ShareReadOnly);
|
||||
|
||||
/**
|
||||
* An IOFile is a lightweight wrapper on C Library file operations.
|
||||
* Automatically closes an open file on the destruction of an IOFile object.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
explicit IOFile(const std::filesystem::path& path, FileAccessMode mode,
|
||||
FileType type = FileType::BinaryFile,
|
||||
FileShareFlag flag = FileShareFlag::ShareReadOnly);
|
||||
|
||||
virtual ~IOFile();
|
||||
|
||||
IOFile(IOFile&& other) noexcept;
|
||||
IOFile& operator=(IOFile&& other) noexcept;
|
||||
|
||||
/**
|
||||
* Gets the path of the file.
|
||||
*
|
||||
* @returns The path of the file.
|
||||
*/
|
||||
[[nodiscard]] std::filesystem::path GetPath() const;
|
||||
|
||||
/**
|
||||
* Gets the access mode of the file.
|
||||
*
|
||||
* @returns The access mode of the file.
|
||||
*/
|
||||
[[nodiscard]] FileAccessMode GetAccessMode() const;
|
||||
|
||||
/**
|
||||
* Gets the type of the file.
|
||||
*
|
||||
* @returns The type of the file.
|
||||
*/
|
||||
[[nodiscard]] FileType GetType() const;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
void Open(const std::filesystem::path& path, FileAccessMode mode,
|
||||
FileType type = FileType::BinaryFile,
|
||||
FileShareFlag flag = FileShareFlag::ShareReadOnly);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] void Open(const Path& path, FileAccessMode mode,
|
||||
FileType type = FileType::BinaryFile,
|
||||
FileShareFlag flag = FileShareFlag::ShareReadOnly) {
|
||||
using ValueType = typename Path::value_type;
|
||||
if constexpr (IsChar<ValueType>) {
|
||||
Open(ToU8String(path), mode, type, flag);
|
||||
} else {
|
||||
Open(std::filesystem::path{path}, mode, type, flag);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Closes the file if it is opened.
|
||||
void Close();
|
||||
|
||||
/**
|
||||
* Checks whether the file is open.
|
||||
* Use this to check whether the calls to Open() or Close() succeeded.
|
||||
*
|
||||
* @returns True if the file is open, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool IsOpen() const;
|
||||
|
||||
/**
|
||||
* Helper function which deduces the value type of a contiguous STL container used in ReadSpan.
|
||||
* If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls
|
||||
* ReadObject and T must be a trivially copyable object.
|
||||
*
|
||||
* See ReadSpan for more details if T is a contiguous container.
|
||||
* See ReadObject for more details if T is a trivially copyable object.
|
||||
*
|
||||
* @tparam T Contiguous container or trivially copyable object
|
||||
*
|
||||
* @param data Container of T::value_type data or reference to object
|
||||
*
|
||||
* @returns Count of T::value_type data or objects successfully read.
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] size_t Read(T& data) const {
|
||||
if constexpr (IsSTLContainer<T>) {
|
||||
using ContiguousType = typename T::value_type;
|
||||
static_assert(std::is_trivially_copyable_v<ContiguousType>,
|
||||
"Data type must be trivially copyable.");
|
||||
return ReadSpan<ContiguousType>(data);
|
||||
} else {
|
||||
return ReadObject(data) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function which deduces the value type of a contiguous STL container used in WriteSpan.
|
||||
* If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls
|
||||
* WriteObject and T must be a trivially copyable object.
|
||||
*
|
||||
* See WriteSpan for more details if T is a contiguous container.
|
||||
* See WriteObject for more details if T is a trivially copyable object.
|
||||
*
|
||||
* @tparam T Contiguous container or trivially copyable object
|
||||
*
|
||||
* @param data Container of T::value_type data or const reference to object
|
||||
*
|
||||
* @returns Count of T::value_type data or objects successfully written.
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] size_t Write(const T& data) const {
|
||||
if constexpr (IsSTLContainer<T>) {
|
||||
using ContiguousType = typename T::value_type;
|
||||
static_assert(std::is_trivially_copyable_v<ContiguousType>,
|
||||
"Data type must be trivially copyable.");
|
||||
return WriteSpan<ContiguousType>(data);
|
||||
} else {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
|
||||
return WriteObject(data) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a span of T data from a file sequentially.
|
||||
* This function reads from the current position of the file pointer and
|
||||
* advances it by the (count of T * sizeof(T)) bytes successfully read.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
* - The opened file lacks read permissions
|
||||
* - Attempting to read beyond the end-of-file
|
||||
*
|
||||
* @tparam T Data type
|
||||
*
|
||||
* @param data Span of T data
|
||||
*
|
||||
* @returns Count of T data successfully read.
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] size_t ReadSpan(std::span<T> data) const {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
|
||||
|
||||
if (!IsOpen()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return std::fread(data.data(), sizeof(T), data.size(), file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a span of T data to a file sequentially.
|
||||
* This function writes from the current position of the file pointer and
|
||||
* advances it by the (count of T * sizeof(T)) bytes successfully written.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
* - The opened file lacks write permissions
|
||||
*
|
||||
* @tparam T Data type
|
||||
*
|
||||
* @param data Span of T data
|
||||
*
|
||||
* @returns Count of T data successfully written.
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] size_t WriteSpan(std::span<const T> data) const {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
|
||||
|
||||
if (!IsOpen()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return std::fwrite(data.data(), sizeof(T), data.size(), file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a T object from a file sequentially.
|
||||
* This function reads from the current position of the file pointer and
|
||||
* advances it by the sizeof(T) bytes successfully read.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
* - The opened file lacks read permissions
|
||||
* - Attempting to read beyond the end-of-file
|
||||
*
|
||||
* @tparam T Data type
|
||||
*
|
||||
* @param object Reference to object
|
||||
*
|
||||
* @returns True if the object is successfully read from the file, false otherwise.
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] bool ReadObject(T& object) const {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
|
||||
static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
|
||||
|
||||
if (!IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::fread(&object, sizeof(T), 1, file) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a T object to a file sequentially.
|
||||
* This function writes from the current position of the file pointer and
|
||||
* advances it by the sizeof(T) bytes successfully written.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
* - The opened file lacks write permissions
|
||||
*
|
||||
* @tparam T Data type
|
||||
*
|
||||
* @param object Const reference to object
|
||||
*
|
||||
* @returns True if the object is successfully written to the file, false otherwise.
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] bool WriteObject(const T& object) const {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
|
||||
static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
|
||||
|
||||
if (!IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::fwrite(&object, sizeof(T), 1, file) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialized function to read a string of a given length from a file sequentially.
|
||||
* This function writes from the current position of the file pointer and
|
||||
* advances it by the number of characters successfully read.
|
||||
* The size of the returned string may not match length if not all bytes are successfully read.
|
||||
*
|
||||
* @param length Length of the string
|
||||
*
|
||||
* @returns A string read from the file.
|
||||
*/
|
||||
[[nodiscard]] std::string ReadString(size_t length) const;
|
||||
|
||||
/**
|
||||
* Specialized function to write a string to a file sequentially.
|
||||
* This function writes from the current position of the file pointer and
|
||||
* advances it by the number of characters successfully written.
|
||||
*
|
||||
* @param string Span of const char backed std::string or std::string_view
|
||||
*
|
||||
* @returns Number of characters successfully written.
|
||||
*/
|
||||
[[nodiscard]] size_t WriteString(std::span<const char> string) const;
|
||||
|
||||
/**
|
||||
* Flushes any unwritten buffered data into the file.
|
||||
*
|
||||
* @returns True if the flush was successful, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool Flush() const;
|
||||
|
||||
/**
|
||||
* Resizes the file to a given size.
|
||||
* If the file is resized to a smaller size, the remainder of the file is discarded.
|
||||
* If the file is resized to a larger size, the new area appears as if zero-filled.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
*
|
||||
* @param size File size in bytes
|
||||
*
|
||||
* @returns True if the file resize succeeded, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool SetSize(u64 size) const;
|
||||
|
||||
/**
|
||||
* Gets the size of the file.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
*
|
||||
* @returns The file size in bytes of the file. Returns 0 on failure.
|
||||
*/
|
||||
[[nodiscard]] u64 GetSize() const;
|
||||
|
||||
/**
|
||||
* Moves the current position of the file pointer with the specified offset and seek origin.
|
||||
*
|
||||
* @param offset Offset from seek origin
|
||||
* @param origin Seek origin
|
||||
*
|
||||
* @returns True if the file pointer has moved to the specified offset, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const;
|
||||
|
||||
/**
|
||||
* Gets the current position of the file pointer.
|
||||
*
|
||||
* @returns The current position of the file pointer.
|
||||
*/
|
||||
[[nodiscard]] s64 Tell() const;
|
||||
|
||||
private:
|
||||
std::filesystem::path file_path;
|
||||
FileAccessMode file_access_mode;
|
||||
FileType file_type;
|
||||
|
||||
std::FILE* file = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Common::FS
|
@ -0,0 +1,610 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.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<IOFile> 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 (!IsFile(path)) {
|
||||
LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file",
|
||||
PathToUTF8String(path));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto io_file = std::make_shared<IOFile>(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;
|
||||
|
||||
for (const auto& entry : fs::recursive_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;
|
||||
}
|
||||
}
|
||||
|
||||
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.path())) {
|
||||
callback_error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (True(filter & DirEntryFilter::Directory) &&
|
||||
entry.status().type() == fs::file_type::directory) {
|
||||
if (!callback(entry.path())) {
|
||||
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;
|
||||
|
||||
for (const auto& entry : fs::recursive_directory_iterator(path, ec)) {
|
||||
if (ec) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (True(filter & DirEntryFilter::File) &&
|
||||
entry.status().type() == fs::file_type::regular) {
|
||||
if (!callback(entry.path())) {
|
||||
callback_error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (True(filter & DirEntryFilter::Directory) &&
|
||||
entry.status().type() == fs::file_type::directory) {
|
||||
if (!callback(entry.path())) {
|
||||
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));
|
||||
}
|
||||
|
||||
// Generic Filesystem Operations
|
||||
|
||||
bool Exists(const fs::path& path) {
|
||||
return fs::exists(path);
|
||||
}
|
||||
|
||||
bool IsFile(const fs::path& path) {
|
||||
return fs::is_regular_file(path);
|
||||
}
|
||||
|
||||
bool IsDir(const fs::path& path) {
|
||||
return fs::is_directory(path);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
u64 GetSize(const fs::path& path) {
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace Common::FS
|
@ -0,0 +1,582 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
#include "common/fs/fs_types.h"
|
||||
#include "common/fs/fs_util.h"
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
class IOFile;
|
||||
|
||||
// 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 <typename Path>
|
||||
[[nodiscard]] bool NewFile(const Path& path, u64 size = 0) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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 file
|
||||
* - Filesystem at path is read only
|
||||
*
|
||||
* @param path Filesystem path
|
||||
*
|
||||
* @returns True if file removal succeeds or file does not exist, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool RemoveFile(const std::filesystem::path& path);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] bool RemoveFile(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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 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 <typename Path1, typename Path2>
|
||||
[[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<ValueType1> && IsChar<ValueType2>) {
|
||||
return RenameFile(ToU8String(old_path), ToU8String(new_path));
|
||||
} else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
|
||||
return RenameFile(ToU8String(old_path), new_path);
|
||||
} else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
|
||||
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 is not a file
|
||||
* - The file is not opened
|
||||
*
|
||||
* @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<IOFile> FileOpen(const std::filesystem::path& path,
|
||||
FileAccessMode mode,
|
||||
FileType type = FileType::BinaryFile,
|
||||
FileShareFlag flag = FileShareFlag::ShareReadOnly);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] std::shared_ptr<IOFile> FileOpen(const Path& path, FileAccessMode mode,
|
||||
FileType type = FileType::BinaryFile,
|
||||
FileShareFlag flag = FileShareFlag::ShareReadOnly) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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 <typename Path>
|
||||
[[nodiscard]] bool CreateDir(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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 <typename Path>
|
||||
[[nodiscard]] bool CreateDirs(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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 <typename Path>
|
||||
[[nodiscard]] bool CreateParentDir(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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 <typename Path>
|
||||
[[nodiscard]] bool CreateParentDirs(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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.
|
||||
*/
|
||||
[[nodiscard]] bool RemoveDir(const std::filesystem::path& path);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] bool RemoveDir(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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.
|
||||
*/
|
||||
[[nodiscard]] bool RemoveDirRecursively(const std::filesystem::path& path);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] bool RemoveDirRecursively(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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.
|
||||
*/
|
||||
[[nodiscard]] bool RemoveDirContentsRecursively(const std::filesystem::path& path);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] bool RemoveDirContentsRecursively(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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 <typename Path1, typename Path2>
|
||||
[[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<ValueType1> && IsChar<ValueType2>) {
|
||||
return RenameDir(ToU8String(old_path), ToU8String(new_path));
|
||||
} else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
|
||||
return RenameDir(ToU8String(old_path), new_path);
|
||||
} else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
|
||||
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 <typename Path>
|
||||
void IterateDirEntries(const Path& path, const DirEntryCallable& callback,
|
||||
DirEntryFilter filter = DirEntryFilter::All) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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 <typename Path>
|
||||
void IterateDirEntriesRecursively(const Path& path, const DirEntryCallable& callback,
|
||||
DirEntryFilter filter = DirEntryFilter::All) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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 <typename Path>
|
||||
[[nodiscard]] bool Exists(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
return Exists(ToU8String(path));
|
||||
} else {
|
||||
return Exists(std::filesystem::path{path});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns whether a filesystem object at path is a file.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
*
|
||||
* @returns True if a filesystem object at path is a file, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool IsFile(const std::filesystem::path& path);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] bool IsFile(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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 <typename Path>
|
||||
[[nodiscard]] bool IsDir(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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 <typename Path>
|
||||
[[nodiscard]] bool SetCurrentDir(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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 <typename Path>
|
||||
[[nodiscard]] std::filesystem::file_type GetEntryType(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
return GetEntryType(ToU8String(path));
|
||||
} else {
|
||||
return GetEntryType(std::filesystem::path{path});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 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 <typename Path>
|
||||
[[nodiscard]] u64 GetSize(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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 <typename Path>
|
||||
[[nodiscard]] u64 GetFreeSpaceSize(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
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 <typename Path>
|
||||
[[nodiscard]] u64 GetTotalSpaceSize(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
return GetTotalSpaceSize(ToU8String(path));
|
||||
} else {
|
||||
return GetTotalSpaceSize(std::filesystem::path{path});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Common::FS
|
@ -0,0 +1,27 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
// yuzu data directories
|
||||
|
||||
#define YUZU_DIR "yuzu"
|
||||
#define PORTABLE_DIR "user"
|
||||
|
||||
// Sub-directories contained within a yuzu data directory
|
||||
|
||||
#define CACHE_DIR "cache"
|
||||
#define CONFIG_DIR "config"
|
||||
#define DUMP_DIR "dump"
|
||||
#define KEYS_DIR "keys"
|
||||
#define LOAD_DIR "load"
|
||||
#define LOG_DIR "log"
|
||||
#define NAND_DIR "nand"
|
||||
#define SCREENSHOTS_DIR "screenshots"
|
||||
#define SDMC_DIR "sdmc"
|
||||
#define SHADER_DIR "shader"
|
||||
|
||||
// yuzu-specific files
|
||||
|
||||
#define LOG_FILE "yuzu_log.txt"
|
@ -0,0 +1,73 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
enum class FileAccessMode {
|
||||
/**
|
||||
* If the file at path exists, it opens the file for reading.
|
||||
* If the file at path does not exist, it fails to open the file.
|
||||
*/
|
||||
Read = 1 << 0,
|
||||
/**
|
||||
* If the file at path exists, the existing contents of the file are erased.
|
||||
* The empty file is then opened for writing.
|
||||
* If the file at path does not exist, it creates and opens a new empty file for writing.
|
||||
*/
|
||||
Write = 1 << 1,
|
||||
/**
|
||||
* If the file at path exists, it opens the file for reading and writing.
|
||||
* If the file at path does not exist, it fails to open the file.
|
||||
*/
|
||||
ReadWrite = Read | Write,
|
||||
/**
|
||||
* If the file at path exists, it opens the file for appending.
|
||||
* If the file at path does not exist, it creates and opens a new empty file for appending.
|
||||
*/
|
||||
Append = 1 << 2,
|
||||
/**
|
||||
* If the file at path exists, it opens the file for both reading and appending.
|
||||
* If the file at path does not exist, it creates and opens a new empty file for both
|
||||
* reading and appending.
|
||||
*/
|
||||
ReadAppend = Read | Append,
|
||||
};
|
||||
|
||||
enum class FileType {
|
||||
BinaryFile,
|
||||
TextFile,
|
||||
};
|
||||
|
||||
enum class FileShareFlag {
|
||||
ShareNone, // Provides exclusive access to the file.
|
||||
ShareReadOnly, // Provides read only shared access to the file.
|
||||
ShareWriteOnly, // Provides write only shared access to the file.
|
||||
ShareReadWrite, // Provides read and write shared access to the file.
|
||||
};
|
||||
|
||||
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<bool(const std::filesystem::path& path)>;
|
||||
|
||||
} // namespace Common::FS
|
@ -0,0 +1,13 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/fs/fs_util.h"
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
std::u8string ToU8String(std::string_view utf8_string) {
|
||||
return std::u8string{utf8_string.begin(), utf8_string.end()};
|
||||
}
|
||||
|
||||
} // namespace Common::FS
|
@ -0,0 +1,25 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
template <typename T>
|
||||
concept IsChar = std::same_as<T, char>;
|
||||
|
||||
/**
|
||||
* Converts a UTF-8 encoded std::string or std::string_view to a std::u8string.
|
||||
*
|
||||
* @param utf8_string UTF-8 encoded string
|
||||
*
|
||||
* @returns UTF-8 encoded std::u8string.
|
||||
*/
|
||||
[[nodiscard]] std::u8string ToU8String(std::string_view utf8_string);
|
||||
|
||||
} // namespace Common::FS
|
@ -0,0 +1,432 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/fs_paths.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <shlobj.h> // Used in GetExeDirectory()
|
||||
#else
|
||||
#include <cstdlib> // Used in Get(Home/Data)Directory()
|
||||
#include <pwd.h> // Used in GetHomeDirectory()
|
||||
#include <sys/types.h> // Used in GetHomeDirectory()
|
||||
#include <unistd.h> // Used in GetDataDirectory()
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <sys/param.h> // Used in GetBundleDirectory()
|
||||
|
||||
// CFURL contains __attribute__ directives that gcc does not know how to parse, so we need to just
|
||||
// ignore them if we're not using clang. The macro is only used to prevent linking against
|
||||
// functions that don't exist on older versions of macOS, and the worst case scenario is a linker
|
||||
// error, so this is perfectly safe, just inconvenient.
|
||||
#ifndef __clang__
|
||||
#define availability(...)
|
||||
#endif
|
||||
#include <CoreFoundation/CFBundle.h> // Used in GetBundleDirectory()
|
||||
#include <CoreFoundation/CFString.h> // Used in GetBundleDirectory()
|
||||
#include <CoreFoundation/CFURL.h> // Used in GetBundleDirectory()
|
||||
#ifdef availability
|
||||
#undef availability
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MAX_PATH
|
||||
#ifdef _WIN32
|
||||
// This is the maximum number of UTF-16 code units permissible in Windows file paths
|
||||
#define MAX_PATH 260
|
||||
#else
|
||||
// This is the maximum number of UTF-8 code units permissible in all other OSes' file paths
|
||||
#define MAX_PATH 1024
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/**
|
||||
* The PathManagerImpl is a singleton allowing to manage the mapping of
|
||||
* YuzuPath enums to real filesystem paths.
|
||||
* This class provides 2 functions: GetYuzuPathImpl and SetYuzuPathImpl.
|
||||
* These are used by GetYuzuPath and SetYuzuPath respectively to get or modify
|
||||
* the path mapped by the YuzuPath enum.
|
||||
*/
|
||||
class PathManagerImpl {
|
||||
public:
|
||||
static PathManagerImpl& GetInstance() {
|
||||
static PathManagerImpl path_manager_impl;
|
||||
|
||||
return path_manager_impl;
|
||||
}
|
||||
|
||||
PathManagerImpl(const PathManagerImpl&) = delete;
|
||||
PathManagerImpl& operator=(const PathManagerImpl&) = delete;
|
||||
|
||||
PathManagerImpl(PathManagerImpl&&) = delete;
|
||||
PathManagerImpl& operator=(PathManagerImpl&&) = delete;
|
||||
|
||||
[[nodiscard]] const fs::path& GetYuzuPathImpl(YuzuPath yuzu_path) {
|
||||
return yuzu_paths.at(yuzu_path);
|
||||
}
|
||||
|
||||
void SetYuzuPathImpl(YuzuPath yuzu_path, const fs::path& new_path) {
|
||||
yuzu_paths.insert_or_assign(yuzu_path, new_path);
|
||||
}
|
||||
|
||||
private:
|
||||
PathManagerImpl() {
|
||||
#ifdef _WIN32
|
||||
auto yuzu_path = GetExeDirectory() / PORTABLE_DIR;
|
||||
|
||||
if (!IsDir(yuzu_path)) {
|
||||
yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR;
|
||||
}
|
||||
|
||||
GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
|
||||
GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path / CACHE_DIR);
|
||||
GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path / CONFIG_DIR);
|
||||
#else
|
||||
auto yuzu_path = GetCurrentDir() / PORTABLE_DIR;
|
||||
|
||||
if (Exists(yuzu_path) && IsDir(yuzu_path)) {
|
||||
GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
|
||||
GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path / CACHE_DIR);
|
||||
GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path / CONFIG_DIR);
|
||||
} else {
|
||||
yuzu_path = GetDataDirectory("XDG_DATA_HOME") / YUZU_DIR;
|
||||
|
||||
GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
|
||||
GenerateYuzuPath(YuzuPath::CacheDir, GetDataDirectory("XDG_CACHE_HOME") / YUZU_DIR);
|
||||
GenerateYuzuPath(YuzuPath::ConfigDir, GetDataDirectory("XDG_CONFIG_HOME") / YUZU_DIR);
|
||||
}
|
||||
#endif
|
||||
|
||||
GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR);
|
||||
GenerateYuzuPath(YuzuPath::KeysDir, yuzu_path / KEYS_DIR);
|
||||
GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
|
||||
GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR);
|
||||
GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR);
|
||||
GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
|
||||
GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
|
||||
GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
|
||||
}
|
||||
|
||||
~PathManagerImpl() = default;
|
||||
|
||||
void GenerateYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) {
|
||||
void(FS::CreateDir(new_path));
|
||||
|
||||
SetYuzuPathImpl(yuzu_path, new_path);
|
||||
}
|
||||
|
||||
std::unordered_map<YuzuPath, fs::path> yuzu_paths;
|
||||
};
|
||||
|
||||
std::string PathToUTF8String(const fs::path& path) {
|
||||
const auto utf8_string = path.u8string();
|
||||
|
||||
return std::string{utf8_string.begin(), utf8_string.end()};
|
||||
}
|
||||
|
||||
bool ValidatePath(const fs::path& path) {
|
||||
if (path.empty()) {
|
||||
LOG_ERROR(Common_Filesystem, "Input path is empty, path={}", PathToUTF8String(path));
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (path.u16string().size() >= MAX_PATH) {
|
||||
LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path));
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (path.u8string().size() >= MAX_PATH) {
|
||||
LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fs::path ConcatPath(const fs::path& first, const fs::path& second) {
|
||||
const bool second_has_dir_sep = IsDirSeparator(second.u8string().front());
|
||||
|
||||
if (!second_has_dir_sep) {
|
||||
return (first / second).lexically_normal();
|
||||
}
|
||||
|
||||
fs::path concat_path = first;
|
||||
concat_path += second;
|
||||
|
||||
return concat_path.lexically_normal();
|
||||
}
|
||||
|
||||
fs::path ConcatPathSafe(const fs::path& base, const fs::path& offset) {
|
||||
const auto concatenated_path = ConcatPath(base, offset);
|
||||
|
||||
if (!IsPathSandboxed(base, concatenated_path)) {
|
||||
return base;
|
||||
}
|
||||
|
||||
return concatenated_path;
|
||||
}
|
||||
|
||||
bool IsPathSandboxed(const fs::path& base, const fs::path& path) {
|
||||
const auto base_string = RemoveTrailingSeparators(base.lexically_normal()).u8string();
|
||||
const auto path_string = RemoveTrailingSeparators(path.lexically_normal()).u8string();
|
||||
|
||||
if (path_string.size() < base_string.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return base_string.compare(0, base_string.size(), path_string, 0, base_string.size()) == 0;
|
||||
}
|
||||
|
||||
bool IsDirSeparator(char character) {
|
||||
return character == '/' || character == '\\';
|
||||
}
|
||||
|
||||
bool IsDirSeparator(char8_t character) {
|
||||
return character == u8'/' || character == u8'\\';
|
||||
}
|
||||
|
||||
fs::path RemoveTrailingSeparators(const fs::path& path) {
|
||||
if (path.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
auto string_path = path.u8string();
|
||||
|
||||
while (IsDirSeparator(string_path.back())) {
|
||||
string_path.pop_back();
|
||||
}
|
||||
|
||||
return fs::path{string_path};
|
||||
}
|
||||
|
||||
const fs::path& GetYuzuPath(YuzuPath yuzu_path) {
|
||||
return PathManagerImpl::GetInstance().GetYuzuPathImpl(yuzu_path);
|
||||
}
|
||||
|
||||
std::string GetYuzuPathString(YuzuPath yuzu_path) {
|
||||
return PathToUTF8String(GetYuzuPath(yuzu_path));
|
||||
}
|
||||
|
||||
void SetYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) {
|
||||
if (!FS::IsDir(new_path)) {
|
||||
LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} is not a directory",
|
||||
PathToUTF8String(new_path));
|
||||
return;
|
||||
}
|
||||
|
||||
PathManagerImpl::GetInstance().SetYuzuPathImpl(yuzu_path, new_path);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
fs::path GetExeDirectory() {
|
||||
wchar_t exe_path[MAX_PATH];
|
||||
|
||||
GetModuleFileNameW(nullptr, exe_path, MAX_PATH);
|
||||
|
||||
if (!exe_path) {
|
||||
LOG_ERROR(Common_Filesystem,
|
||||
"Failed to get the path to the executable of the current process");
|
||||
}
|
||||
|
||||
return fs::path{exe_path}.parent_path();
|
||||
}
|
||||
|
||||
fs::path GetAppDataRoamingDirectory() {
|
||||
PWSTR appdata_roaming_path = nullptr;
|
||||
|
||||
SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &appdata_roaming_path);
|
||||
|
||||
auto fs_appdata_roaming_path = fs::path{appdata_roaming_path};
|
||||
|
||||
CoTaskMemFree(appdata_roaming_path);
|
||||
|
||||
if (fs_appdata_roaming_path.empty()) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to get the path to the %APPDATA% directory");
|
||||
}
|
||||
|
||||
return fs_appdata_roaming_path;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
fs::path GetHomeDirectory() {
|
||||
const char* home_env_var = getenv("HOME");
|
||||
|
||||
if (home_env_var) {
|
||||
return fs::path{home_env_var};
|
||||
}
|
||||
|
||||
LOG_INFO(Common_Filesystem,
|
||||
"$HOME is not defined in the environment variables, "
|
||||
"attempting to query passwd to get the home path of the current user");
|
||||
|
||||
const auto* pw = getpwuid(getuid());
|
||||
|
||||
if (!pw) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to get the home path of the current user");
|
||||
return {};
|
||||
}
|
||||
|
||||
return fs::path{pw->pw_dir};
|
||||
}
|
||||
|
||||
fs::path GetDataDirectory(const std::string& env_name) {
|
||||
const char* data_env_var = getenv(env_name.c_str());
|
||||
|
||||
if (data_env_var) {
|
||||
return fs::path{data_env_var};
|
||||
}
|
||||
|
||||
if (env_name == "XDG_DATA_HOME") {
|
||||
return GetHomeDirectory() / ".local/share";
|
||||
} else if (env_name == "XDG_CACHE_HOME") {
|
||||
return GetHomeDirectory() / ".cache";
|
||||
} else if (env_name == "XDG_CONFIG_HOME") {
|
||||
return GetHomeDirectory() / ".config";
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
fs::path GetBundleDirectory() {
|
||||
char app_bundle_path[MAXPATHLEN];
|
||||
|
||||
// Get the main bundle for the app
|
||||
CFURLRef bundle_ref = CFBundleCopyBundleURL(CFBundleGetMainBundle());
|
||||
CFStringRef bundle_path = CFURLCopyFileSystemPath(bundle_ref, kCFURLPOSIXPathStyle);
|
||||
|
||||
CFStringGetFileSystemRepresentation(bundle_path, app_bundle_path, sizeof(app_bundle_path));
|
||||
|
||||
CFRelease(bundle_ref);
|
||||
CFRelease(bundle_path);
|
||||
|
||||
return fs::path{app_bundle_path};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// vvvvvvvvvv Deprecated vvvvvvvvvv //
|
||||
|
||||
std::string_view RemoveTrailingSlash(std::string_view path) {
|
||||
if (path.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (path.back() == '\\' || path.back() == '/') {
|
||||
path.remove_suffix(1);
|
||||
return path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitPathComponents(std::string_view filename) {
|
||||
std::string copy(filename);
|
||||
std::replace(copy.begin(), copy.end(), '\\', '/');
|
||||
std::vector<std::string> out;
|
||||
|
||||
std::stringstream stream(copy);
|
||||
std::string item;
|
||||
while (std::getline(stream, item, '/')) {
|
||||
out.push_back(std::move(item));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) {
|
||||
std::string path(path_);
|
||||
char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\';
|
||||
char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/';
|
||||
|
||||
if (directory_separator == DirectorySeparator::PlatformDefault) {
|
||||
#ifdef _WIN32
|
||||
type1 = '/';
|
||||
type2 = '\\';
|
||||
#endif
|
||||
}
|
||||
|
||||
std::replace(path.begin(), path.end(), type1, type2);
|
||||
|
||||
auto start = path.begin();
|
||||
#ifdef _WIN32
|
||||
// allow network paths which start with a double backslash (e.g. \\server\share)
|
||||
if (start != path.end())
|
||||
++start;
|
||||
#endif
|
||||
path.erase(std::unique(start, path.end(),
|
||||
[type2](char c1, char c2) { return c1 == type2 && c2 == type2; }),
|
||||
path.end());
|
||||
return std::string(RemoveTrailingSlash(path));
|
||||
}
|
||||
|
||||
std::string_view GetParentPath(std::string_view path) {
|
||||
const auto name_bck_index = path.rfind('\\');
|
||||
const auto name_fwd_index = path.rfind('/');
|
||||
std::size_t name_index;
|
||||
|
||||
if (name_bck_index == std::string_view::npos || name_fwd_index == std::string_view::npos) {
|
||||
name_index = std::min(name_bck_index, name_fwd_index);
|
||||
} else {
|
||||
name_index = std::max(name_bck_index, name_fwd_index);
|
||||
}
|
||||
|
||||
return path.substr(0, name_index);
|
||||
}
|
||||
|
||||
std::string_view GetPathWithoutTop(std::string_view path) {
|
||||
if (path.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
while (path[0] == '\\' || path[0] == '/') {
|
||||
path.remove_prefix(1);
|
||||
if (path.empty()) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
const auto name_bck_index = path.find('\\');
|
||||
const auto name_fwd_index = path.find('/');
|
||||
return path.substr(std::min(name_bck_index, name_fwd_index) + 1);
|
||||
}
|
||||
|
||||
std::string_view GetFilename(std::string_view path) {
|
||||
const auto name_index = path.find_last_of("\\/");
|
||||
|
||||
if (name_index == std::string_view::npos) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return path.substr(name_index + 1);
|
||||
}
|
||||
|
||||
std::string_view GetExtensionFromFilename(std::string_view name) {
|
||||
const std::size_t index = name.rfind('.');
|
||||
|
||||
if (index == std::string_view::npos) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return name.substr(index + 1);
|
||||
}
|
||||
|
||||
} // namespace Common::FS
|
@ -0,0 +1,309 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include "common/fs/fs_util.h"
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
enum class YuzuPath {
|
||||
YuzuDir, // Where yuzu stores its data.
|
||||
CacheDir, // Where cached filesystem data is stored.
|
||||
ConfigDir, // Where config files are stored.
|
||||
DumpDir, // Where dumped data is stored.
|
||||
KeysDir, // Where key files are stored.
|
||||
LoadDir, // Where cheat/mod files are stored.
|
||||
LogDir, // Where log files are stored.
|
||||
NANDDir, // Where the emulated NAND is stored.
|
||||
ScreenshotsDir, // Where yuzu screenshots are stored.
|
||||
SDMCDir, // Where the emulated SDMC is stored.
|
||||
ShaderDir, // Where shaders are stored.
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a filesystem path to a UTF-8 encoded std::string.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
*
|
||||
* @returns UTF-8 encoded std::string.
|
||||
*/
|
||||
[[nodiscard]] std::string PathToUTF8String(const std::filesystem::path& path);
|
||||
|
||||
/**
|
||||
* Validates a given path.
|
||||
*
|
||||
* A given path is valid if it meets these conditions:
|
||||
* - The path is not empty
|
||||
* - The path is not too long
|
||||
*
|
||||
* @param path Filesystem path
|
||||
*
|
||||
* @returns True if the path is valid, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool ValidatePath(const std::filesystem::path& path);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] bool ValidatePath(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
return ValidatePath(ToU8String(path));
|
||||
} else {
|
||||
return ValidatePath(std::filesystem::path{path});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Concatenates two filesystem paths together.
|
||||
*
|
||||
* This is needed since the following occurs when using std::filesystem::path's operator/:
|
||||
* first: "/first/path"
|
||||
* second: "/second/path" (Note that the second path has a directory separator in the front)
|
||||
* first / second yields "/second/path" when the desired result is first/path/second/path
|
||||
*
|
||||
* @param first First filesystem path
|
||||
* @param second Second filesystem path
|
||||
*
|
||||
* @returns A concatenated filesystem path.
|
||||
*/
|
||||
[[nodiscard]] std::filesystem::path ConcatPath(const std::filesystem::path& first,
|
||||
const std::filesystem::path& second);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path1, typename Path2>
|
||||
[[nodiscard]] std::filesystem::path ConcatPath(const Path1& first, const Path2& second) {
|
||||
using ValueType1 = typename Path1::value_type;
|
||||
using ValueType2 = typename Path2::value_type;
|
||||
if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
|
||||
return ConcatPath(ToU8String(first), ToU8String(second));
|
||||
} else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
|
||||
return ConcatPath(ToU8String(first), second);
|
||||
} else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
|
||||
return ConcatPath(first, ToU8String(second));
|
||||
} else {
|
||||
return ConcatPath(std::filesystem::path{first}, std::filesystem::path{second});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Safe variant of ConcatPath that takes in a base path and an offset path from the given base path.
|
||||
*
|
||||
* If ConcatPath(base, offset) resolves to a path that is sandboxed within the base path,
|
||||
* this will return the concatenated path. Otherwise this will return the base path.
|
||||
*
|
||||
* @param base Base filesystem path
|
||||
* @param offset Offset filesystem path
|
||||
*
|
||||
* @returns A concatenated filesystem path if it is within the base path,
|
||||
* returns the base path otherwise.
|
||||
*/
|
||||
[[nodiscard]] std::filesystem::path ConcatPathSafe(const std::filesystem::path& base,
|
||||
const std::filesystem::path& offset);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path1, typename Path2>
|
||||
[[nodiscard]] std::filesystem::path ConcatPathSafe(const Path1& base, const Path2& offset) {
|
||||
using ValueType1 = typename Path1::value_type;
|
||||
using ValueType2 = typename Path2::value_type;
|
||||
if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
|
||||
return ConcatPathSafe(ToU8String(base), ToU8String(offset));
|
||||
} else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
|
||||
return ConcatPathSafe(ToU8String(base), offset);
|
||||
} else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
|
||||
return ConcatPathSafe(base, ToU8String(offset));
|
||||
} else {
|
||||
return ConcatPathSafe(std::filesystem::path{base}, std::filesystem::path{offset});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Checks whether a given path is sandboxed within a given base path.
|
||||
*
|
||||
* @param base Base filesystem path
|
||||
* @param path Filesystem path
|
||||
*
|
||||
* @returns True if the given path is sandboxed within the given base path, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool IsPathSandboxed(const std::filesystem::path& base,
|
||||
const std::filesystem::path& path);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path1, typename Path2>
|
||||
[[nodiscard]] bool IsPathSandboxed(const Path1& base, const Path2& path) {
|
||||
using ValueType1 = typename Path1::value_type;
|
||||
using ValueType2 = typename Path2::value_type;
|
||||
if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
|
||||
return IsPathSandboxed(ToU8String(base), ToU8String(path));
|
||||
} else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
|
||||
return IsPathSandboxed(ToU8String(base), path);
|
||||
} else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
|
||||
return IsPathSandboxed(base, ToU8String(path));
|
||||
} else {
|
||||
return IsPathSandboxed(std::filesystem::path{base}, std::filesystem::path{path});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Checks if a character is a directory separator (either a forward slash or backslash).
|
||||
*
|
||||
* @param character Character
|
||||
*
|
||||
* @returns True if the character is a directory separator, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool IsDirSeparator(char character);
|
||||
|
||||
/**
|
||||
* Checks if a character is a directory separator (either a forward slash or backslash).
|
||||
*
|
||||
* @param character Character
|
||||
*
|
||||
* @returns True if the character is a directory separator, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool IsDirSeparator(char8_t character);
|
||||
|
||||
/**
|
||||
* Removes any trailing directory separators if they exist in the given path.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
*
|
||||
* @returns The filesystem path without any trailing directory separators.
|
||||
*/
|
||||
[[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const std::filesystem::path& path);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const Path& path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
return RemoveTrailingSeparators(ToU8String(path));
|
||||
} else {
|
||||
return RemoveTrailingSeparators(std::filesystem::path{path});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Gets the filesystem path associated with the YuzuPath enum.
|
||||
*
|
||||
* @param yuzu_path YuzuPath enum
|
||||
*
|
||||
* @returns The filesystem path associated with the YuzuPath enum.
|
||||
*/
|
||||
[[nodiscard]] const std::filesystem::path& GetYuzuPath(YuzuPath yuzu_path);
|
||||
|
||||
/**
|
||||
* Gets the filesystem path associated with the YuzuPath enum as a UTF-8 encoded std::string.
|
||||
*
|
||||
* @param yuzu_path YuzuPath enum
|
||||
*
|
||||
* @returns The filesystem path associated with the YuzuPath enum as a UTF-8 encoded std::string.
|
||||
*/
|
||||
[[nodiscard]] std::string GetYuzuPathString(YuzuPath yuzu_path);
|
||||
|
||||
/**
|
||||
* Sets a new filesystem path associated with the YuzuPath enum.
|
||||
* If the filesystem object at new_path is not a directory, this function will not do anything.
|
||||
*
|
||||
* @param yuzu_path YuzuPath enum
|
||||
* @param new_path New filesystem path
|
||||
*/
|
||||
void SetYuzuPath(YuzuPath yuzu_path, const std::filesystem::path& new_path);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
[[nodiscard]] void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) {
|
||||
if constexpr (IsChar<typename Path::value_type>) {
|
||||
SetYuzuPath(yuzu_path, ToU8String(new_path));
|
||||
} else {
|
||||
SetYuzuPath(yuzu_path, std::filesystem::path{new_path});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
/**
|
||||
* Gets the path of the directory containing the executable of the current process.
|
||||
*
|
||||
* @returns The path of the directory containing the executable of the current process.
|
||||
*/
|
||||
[[nodiscard]] std::filesystem::path GetExeDirectory();
|
||||
|
||||
/**
|
||||
* Gets the path of the current user's %APPDATA% directory (%USERPROFILE%/AppData/Roaming).
|
||||
*
|
||||
* @returns The path of the current user's %APPDATA% directory.
|
||||
*/
|
||||
[[nodiscard]] std::filesystem::path GetAppDataRoamingDirectory();
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
* Gets the path of the directory specified by the #HOME environment variable.
|
||||
* If $HOME is not defined, it will attempt to query the user database in passwd instead.
|
||||
*
|
||||
* @returns The path of the current user's home directory.
|
||||
*/
|
||||
[[nodiscard]] std::filesystem::path GetHomeDirectory();
|
||||
|
||||
/**
|
||||
* Gets the relevant paths for yuzu to store its data based on the given XDG environment variable.
|
||||
* See https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
* Defaults to $HOME/.local/share for main application data,
|
||||
* $HOME/.cache for cached data, and $HOME/.config for configuration files.
|
||||
*
|
||||
* @param env_name XDG environment variable name
|
||||
*
|
||||
* @returns The path where yuzu should store its data.
|
||||
*/
|
||||
[[nodiscard]] std::filesystem::path GetDataDirectory(const std::string& env_name);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
[[nodiscard]] std::filesystem::path GetBundleDirectory();
|
||||
|
||||
#endif
|
||||
|
||||
// vvvvvvvvvv Deprecated vvvvvvvvvv //
|
||||
|
||||
// Removes the final '/' or '\' if one exists
|
||||
[[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path);
|
||||
|
||||
enum class DirectorySeparator {
|
||||
ForwardSlash,
|
||||
BackwardSlash,
|
||||
PlatformDefault,
|
||||
};
|
||||
|
||||
// Splits the path on '/' or '\' and put the components into a vector
|
||||
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
|
||||
[[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename);
|
||||
|
||||
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
|
||||
// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
|
||||
[[nodiscard]] std::string SanitizePath(
|
||||
std::string_view path,
|
||||
DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
|
||||
|
||||
// Gets all of the text up to the last '/' or '\' in the path.
|
||||
[[nodiscard]] std::string_view GetParentPath(std::string_view path);
|
||||
|
||||
// Gets all of the text after the first '/' or '\' in the path.
|
||||
[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path);
|
||||
|
||||
// Gets the filename of the path
|
||||
[[nodiscard]] std::string_view GetFilename(std::string_view path);
|
||||
|
||||
// Gets the extension of the filename
|
||||
[[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name);
|
||||
|
||||
} // namespace Common::FS
|
Loading…
Reference in New Issue