diff options
Diffstat (limited to 'src/common/fs')
| -rw-r--r-- | src/common/fs/file.cpp | 392 | ||||
| -rw-r--r-- | src/common/fs/file.h | 450 | ||||
| -rw-r--r-- | src/common/fs/fs.cpp | 610 | ||||
| -rw-r--r-- | src/common/fs/fs.h | 582 | ||||
| -rw-r--r-- | src/common/fs/fs_paths.h | 27 | ||||
| -rw-r--r-- | src/common/fs/fs_types.h | 73 | ||||
| -rw-r--r-- | src/common/fs/fs_util.cpp | 13 | ||||
| -rw-r--r-- | src/common/fs/fs_util.h | 25 | ||||
| -rw-r--r-- | src/common/fs/path_util.cpp | 432 | ||||
| -rw-r--r-- | src/common/fs/path_util.h | 309 |
10 files changed, 2913 insertions, 0 deletions
diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp new file mode 100644 index 000000000..9f3de1cb0 --- /dev/null +++ b/src/common/fs/file.cpp | |||
| @@ -0,0 +1,392 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "common/fs/file.h" | ||
| 6 | #include "common/fs/fs.h" | ||
| 7 | #include "common/fs/path_util.h" | ||
| 8 | #include "common/logging/log.h" | ||
| 9 | |||
| 10 | #ifdef _WIN32 | ||
| 11 | #include <io.h> | ||
| 12 | #include <share.h> | ||
| 13 | #else | ||
| 14 | #include <unistd.h> | ||
| 15 | #endif | ||
| 16 | |||
| 17 | #ifdef _MSC_VER | ||
| 18 | #define fileno _fileno | ||
| 19 | #define fseeko _fseeki64 | ||
| 20 | #define ftello _ftelli64 | ||
| 21 | #endif | ||
| 22 | |||
| 23 | namespace Common::FS { | ||
| 24 | |||
| 25 | namespace fs = std::filesystem; | ||
| 26 | |||
| 27 | namespace { | ||
| 28 | |||
| 29 | #ifdef _WIN32 | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Converts the file access mode and file type enums to a file access mode wide string. | ||
| 33 | * | ||
| 34 | * @param mode File access mode | ||
| 35 | * @param type File type | ||
| 36 | * | ||
| 37 | * @returns A pointer to a wide string representing the file access mode. | ||
| 38 | */ | ||
| 39 | [[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileType type) { | ||
| 40 | switch (type) { | ||
| 41 | case FileType::BinaryFile: | ||
| 42 | switch (mode) { | ||
| 43 | case FileAccessMode::Read: | ||
| 44 | return L"rb"; | ||
| 45 | case FileAccessMode::Write: | ||
| 46 | return L"wb"; | ||
| 47 | case FileAccessMode::Append: | ||
| 48 | return L"ab"; | ||
| 49 | case FileAccessMode::ReadWrite: | ||
| 50 | return L"r+b"; | ||
| 51 | case FileAccessMode::ReadAppend: | ||
| 52 | return L"a+b"; | ||
| 53 | } | ||
| 54 | break; | ||
| 55 | case FileType::TextFile: | ||
| 56 | switch (mode) { | ||
| 57 | case FileAccessMode::Read: | ||
| 58 | return L"r"; | ||
| 59 | case FileAccessMode::Write: | ||
| 60 | return L"w"; | ||
| 61 | case FileAccessMode::Append: | ||
| 62 | return L"a"; | ||
| 63 | case FileAccessMode::ReadWrite: | ||
| 64 | return L"r+"; | ||
| 65 | case FileAccessMode::ReadAppend: | ||
| 66 | return L"a+"; | ||
| 67 | } | ||
| 68 | break; | ||
| 69 | } | ||
| 70 | |||
| 71 | return L""; | ||
| 72 | } | ||
| 73 | |||
| 74 | /** | ||
| 75 | * Converts the file-share access flag enum to a Windows defined file-share access flag. | ||
| 76 | * | ||
| 77 | * @param flag File-share access flag | ||
| 78 | * | ||
| 79 | * @returns Windows defined file-share access flag. | ||
| 80 | */ | ||
| 81 | [[nodiscard]] constexpr int ToWindowsFileShareFlag(FileShareFlag flag) { | ||
| 82 | switch (flag) { | ||
| 83 | case FileShareFlag::ShareNone: | ||
| 84 | default: | ||
| 85 | return _SH_DENYRW; | ||
| 86 | case FileShareFlag::ShareReadOnly: | ||
| 87 | return _SH_DENYWR; | ||
| 88 | case FileShareFlag::ShareWriteOnly: | ||
| 89 | return _SH_DENYRD; | ||
| 90 | case FileShareFlag::ShareReadWrite: | ||
| 91 | return _SH_DENYNO; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | #else | ||
| 96 | |||
| 97 | /** | ||
| 98 | * Converts the file access mode and file type enums to a file access mode string. | ||
| 99 | * | ||
| 100 | * @param mode File access mode | ||
| 101 | * @param type File type | ||
| 102 | * | ||
| 103 | * @returns A pointer to a string representing the file access mode. | ||
| 104 | */ | ||
| 105 | [[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileType type) { | ||
| 106 | switch (type) { | ||
| 107 | case FileType::BinaryFile: | ||
| 108 | switch (mode) { | ||
| 109 | case FileAccessMode::Read: | ||
| 110 | return "rb"; | ||
| 111 | case FileAccessMode::Write: | ||
| 112 | return "wb"; | ||
| 113 | case FileAccessMode::Append: | ||
| 114 | return "ab"; | ||
| 115 | case FileAccessMode::ReadWrite: | ||
| 116 | return "r+b"; | ||
| 117 | case FileAccessMode::ReadAppend: | ||
| 118 | return "a+b"; | ||
| 119 | } | ||
| 120 | break; | ||
| 121 | case FileType::TextFile: | ||
| 122 | switch (mode) { | ||
| 123 | case FileAccessMode::Read: | ||
| 124 | return "r"; | ||
| 125 | case FileAccessMode::Write: | ||
| 126 | return "w"; | ||
| 127 | case FileAccessMode::Append: | ||
| 128 | return "a"; | ||
| 129 | case FileAccessMode::ReadWrite: | ||
| 130 | return "r+"; | ||
| 131 | case FileAccessMode::ReadAppend: | ||
| 132 | return "a+"; | ||
| 133 | } | ||
| 134 | break; | ||
| 135 | } | ||
| 136 | |||
| 137 | return ""; | ||
| 138 | } | ||
| 139 | |||
| 140 | #endif | ||
| 141 | |||
| 142 | /** | ||
| 143 | * Converts the seek origin enum to a seek origin integer. | ||
| 144 | * | ||
| 145 | * @param origin Seek origin | ||
| 146 | * | ||
| 147 | * @returns Seek origin integer. | ||
| 148 | */ | ||
| 149 | [[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) { | ||
| 150 | switch (origin) { | ||
| 151 | case SeekOrigin::SetOrigin: | ||
| 152 | default: | ||
| 153 | return SEEK_SET; | ||
| 154 | case SeekOrigin::CurrentPosition: | ||
| 155 | return SEEK_CUR; | ||
| 156 | case SeekOrigin::End: | ||
| 157 | return SEEK_END; | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | } // Anonymous namespace | ||
| 162 | |||
| 163 | std::string ReadStringFromFile(const std::filesystem::path& path, FileType type) { | ||
| 164 | if (!IsFile(path)) { | ||
| 165 | return ""; | ||
| 166 | } | ||
| 167 | |||
| 168 | IOFile io_file{path, FileAccessMode::Read, type}; | ||
| 169 | |||
| 170 | return io_file.ReadString(io_file.GetSize()); | ||
| 171 | } | ||
| 172 | |||
| 173 | size_t WriteStringToFile(const std::filesystem::path& path, FileType type, | ||
| 174 | std::string_view string) { | ||
| 175 | if (!IsFile(path)) { | ||
| 176 | return 0; | ||
| 177 | } | ||
| 178 | |||
| 179 | IOFile io_file{path, FileAccessMode::Write, type}; | ||
| 180 | |||
| 181 | return io_file.WriteString(string); | ||
| 182 | } | ||
| 183 | |||
| 184 | size_t AppendStringToFile(const std::filesystem::path& path, FileType type, | ||
| 185 | std::string_view string) { | ||
| 186 | if (!Exists(path)) { | ||
| 187 | return WriteStringToFile(path, type, string); | ||
| 188 | } | ||
| 189 | |||
| 190 | if (!IsFile(path)) { | ||
| 191 | return 0; | ||
| 192 | } | ||
| 193 | |||
| 194 | IOFile io_file{path, FileAccessMode::Append, type}; | ||
| 195 | |||
| 196 | return io_file.WriteString(string); | ||
| 197 | } | ||
| 198 | |||
| 199 | IOFile::IOFile() = default; | ||
| 200 | |||
| 201 | IOFile::IOFile(const std::string& path, FileAccessMode mode, FileType type, FileShareFlag flag) { | ||
| 202 | Open(path, mode, type, flag); | ||
| 203 | } | ||
| 204 | |||
| 205 | IOFile::IOFile(std::string_view path, FileAccessMode mode, FileType type, FileShareFlag flag) { | ||
| 206 | Open(path, mode, type, flag); | ||
| 207 | } | ||
| 208 | |||
| 209 | IOFile::IOFile(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) { | ||
| 210 | Open(path, mode, type, flag); | ||
| 211 | } | ||
| 212 | |||
| 213 | IOFile::~IOFile() { | ||
| 214 | Close(); | ||
| 215 | } | ||
| 216 | |||
| 217 | IOFile::IOFile(IOFile&& other) noexcept { | ||
| 218 | std::swap(file_path, other.file_path); | ||
| 219 | std::swap(file_access_mode, other.file_access_mode); | ||
| 220 | std::swap(file_type, other.file_type); | ||
| 221 | std::swap(file, other.file); | ||
| 222 | } | ||
| 223 | |||
| 224 | IOFile& IOFile::operator=(IOFile&& other) noexcept { | ||
| 225 | std::swap(file_path, other.file_path); | ||
| 226 | std::swap(file_access_mode, other.file_access_mode); | ||
| 227 | std::swap(file_type, other.file_type); | ||
| 228 | std::swap(file, other.file); | ||
| 229 | return *this; | ||
| 230 | } | ||
| 231 | |||
| 232 | fs::path IOFile::GetPath() const { | ||
| 233 | return file_path; | ||
| 234 | } | ||
| 235 | |||
| 236 | FileAccessMode IOFile::GetAccessMode() const { | ||
| 237 | return file_access_mode; | ||
| 238 | } | ||
| 239 | |||
| 240 | FileType IOFile::GetType() const { | ||
| 241 | return file_type; | ||
| 242 | } | ||
| 243 | |||
| 244 | void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) { | ||
| 245 | Close(); | ||
| 246 | |||
| 247 | file_path = path; | ||
| 248 | file_access_mode = mode; | ||
| 249 | file_type = type; | ||
| 250 | |||
| 251 | errno = 0; | ||
| 252 | |||
| 253 | #ifdef _WIN32 | ||
| 254 | if (flag != FileShareFlag::ShareNone) { | ||
| 255 | file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag)); | ||
| 256 | } else { | ||
| 257 | _wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type)); | ||
| 258 | } | ||
| 259 | #else | ||
| 260 | file = std::fopen(path.c_str(), AccessModeToStr(mode, type)); | ||
| 261 | #endif | ||
| 262 | |||
| 263 | if (!IsOpen()) { | ||
| 264 | const auto ec = std::error_code{errno, std::generic_category()}; | ||
| 265 | LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, ec_message={}", | ||
| 266 | PathToUTF8String(file_path), ec.message()); | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | void IOFile::Close() { | ||
| 271 | if (!IsOpen()) { | ||
| 272 | return; | ||
| 273 | } | ||
| 274 | |||
| 275 | errno = 0; | ||
| 276 | |||
| 277 | const auto close_result = std::fclose(file) == 0; | ||
| 278 | |||
| 279 | if (!close_result) { | ||
| 280 | const auto ec = std::error_code{errno, std::generic_category()}; | ||
| 281 | LOG_ERROR(Common_Filesystem, "Failed to close the file at path={}, ec_message={}", | ||
| 282 | PathToUTF8String(file_path), ec.message()); | ||
| 283 | } | ||
| 284 | |||
| 285 | file = nullptr; | ||
| 286 | } | ||
| 287 | |||
| 288 | bool IOFile::IsOpen() const { | ||
| 289 | return file != nullptr; | ||
| 290 | } | ||
| 291 | |||
| 292 | std::string IOFile::ReadString(size_t length) const { | ||
| 293 | std::vector<char> string_buffer(length); | ||
| 294 | |||
| 295 | const auto chars_read = ReadSpan<char>(string_buffer); | ||
| 296 | const auto string_size = chars_read != length ? chars_read : length; | ||
| 297 | |||
| 298 | return std::string{string_buffer.data(), string_size}; | ||
| 299 | } | ||
| 300 | |||
| 301 | size_t IOFile::WriteString(std::span<const char> string) const { | ||
| 302 | return WriteSpan(string); | ||
| 303 | } | ||
| 304 | |||
| 305 | bool IOFile::Flush() const { | ||
| 306 | if (!IsOpen()) { | ||
| 307 | return false; | ||
| 308 | } | ||
| 309 | |||
| 310 | errno = 0; | ||
| 311 | |||
| 312 | const auto flush_result = std::fflush(file) == 0; | ||
| 313 | |||
| 314 | if (!flush_result) { | ||
| 315 | const auto ec = std::error_code{errno, std::generic_category()}; | ||
| 316 | LOG_ERROR(Common_Filesystem, "Failed to flush the file at path={}, ec_message={}", | ||
| 317 | PathToUTF8String(file_path), ec.message()); | ||
| 318 | } | ||
| 319 | |||
| 320 | return flush_result; | ||
| 321 | } | ||
| 322 | |||
| 323 | bool IOFile::SetSize(u64 size) const { | ||
| 324 | if (!IsOpen()) { | ||
| 325 | return false; | ||
| 326 | } | ||
| 327 | |||
| 328 | errno = 0; | ||
| 329 | |||
| 330 | #ifdef _WIN32 | ||
| 331 | const auto set_size_result = _chsize_s(fileno(file), static_cast<s64>(size)) == 0; | ||
| 332 | #else | ||
| 333 | const auto set_size_result = ftruncate(fileno(file), static_cast<s64>(size)) == 0; | ||
| 334 | #endif | ||
| 335 | |||
| 336 | if (!set_size_result) { | ||
| 337 | const auto ec = std::error_code{errno, std::generic_category()}; | ||
| 338 | LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}", | ||
| 339 | PathToUTF8String(file_path), size, ec.message()); | ||
| 340 | } | ||
| 341 | |||
| 342 | return set_size_result; | ||
| 343 | } | ||
| 344 | |||
| 345 | u64 IOFile::GetSize() const { | ||
| 346 | if (!IsOpen()) { | ||
| 347 | return 0; | ||
| 348 | } | ||
| 349 | |||
| 350 | std::error_code ec; | ||
| 351 | |||
| 352 | const auto file_size = fs::file_size(file_path, ec); | ||
| 353 | |||
| 354 | if (ec) { | ||
| 355 | LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", | ||
| 356 | PathToUTF8String(file_path), ec.message()); | ||
| 357 | return 0; | ||
| 358 | } | ||
| 359 | |||
| 360 | return file_size; | ||
| 361 | } | ||
| 362 | |||
| 363 | bool IOFile::Seek(s64 offset, SeekOrigin origin) const { | ||
| 364 | if (!IsOpen()) { | ||
| 365 | return false; | ||
| 366 | } | ||
| 367 | |||
| 368 | errno = 0; | ||
| 369 | |||
| 370 | const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0; | ||
| 371 | |||
| 372 | if (!seek_result) { | ||
| 373 | const auto ec = std::error_code{errno, std::generic_category()}; | ||
| 374 | LOG_ERROR(Common_Filesystem, | ||
| 375 | "Failed to seek the file at path={}, offset={}, origin={}, ec_message={}", | ||
| 376 | PathToUTF8String(file_path), offset, origin, ec.message()); | ||
| 377 | } | ||
| 378 | |||
| 379 | return seek_result; | ||
| 380 | } | ||
| 381 | |||
| 382 | s64 IOFile::Tell() const { | ||
| 383 | if (!IsOpen()) { | ||
| 384 | return 0; | ||
| 385 | } | ||
| 386 | |||
| 387 | errno = 0; | ||
| 388 | |||
| 389 | return ftello(file); | ||
| 390 | } | ||
| 391 | |||
| 392 | } // namespace Common::FS | ||
diff --git a/src/common/fs/file.h b/src/common/fs/file.h new file mode 100644 index 000000000..209f9664b --- /dev/null +++ b/src/common/fs/file.h | |||
| @@ -0,0 +1,450 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <cstdio> | ||
| 8 | #include <filesystem> | ||
| 9 | #include <fstream> | ||
| 10 | #include <span> | ||
| 11 | #include <type_traits> | ||
| 12 | #include <vector> | ||
| 13 | |||
| 14 | #include "common/concepts.h" | ||
| 15 | #include "common/fs/fs_types.h" | ||
| 16 | #include "common/fs/fs_util.h" | ||
| 17 | |||
| 18 | namespace Common::FS { | ||
| 19 | |||
| 20 | enum class SeekOrigin { | ||
| 21 | SetOrigin, // Seeks from the start of the file. | ||
| 22 | CurrentPosition, // Seeks from the current file pointer position. | ||
| 23 | End, // Seeks from the end of the file. | ||
| 24 | }; | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Opens a file stream at path with the specified open mode. | ||
| 28 | * | ||
| 29 | * @param file_stream Reference to file stream | ||
| 30 | * @param path Filesystem path | ||
| 31 | * @param open_mode File stream open mode | ||
| 32 | */ | ||
| 33 | template <typename FileStream> | ||
| 34 | void OpenFileStream(FileStream& file_stream, const std::filesystem::path& path, | ||
| 35 | std::ios_base::openmode open_mode) { | ||
| 36 | file_stream.open(path, open_mode); | ||
| 37 | } | ||
| 38 | |||
| 39 | #ifdef _WIN32 | ||
| 40 | template <typename FileStream, typename Path> | ||
| 41 | void OpenFileStream(FileStream& file_stream, const Path& path, std::ios_base::openmode open_mode) { | ||
| 42 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 43 | file_stream.open(ToU8String(path), open_mode); | ||
| 44 | } else { | ||
| 45 | file_stream.open(std::filesystem::path{path}, open_mode); | ||
| 46 | } | ||
| 47 | } | ||
| 48 | #endif | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Reads an entire file at path and returns a string of the contents read from the file. | ||
| 52 | * If the filesystem object at path is not a file, this function returns an empty string. | ||
| 53 | * | ||
| 54 | * @param path Filesystem path | ||
| 55 | * @param type File type | ||
| 56 | * | ||
| 57 | * @returns A string of the contents read from the file. | ||
| 58 | */ | ||
| 59 | [[nodiscard]] std::string ReadStringFromFile(const std::filesystem::path& path, FileType type); | ||
| 60 | |||
| 61 | #ifdef _WIN32 | ||
| 62 | template <typename Path> | ||
| 63 | [[nodiscard]] std::string ReadStringFromFile(const Path& path, FileType type) { | ||
| 64 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 65 | return ReadStringFromFile(ToU8String(path), type); | ||
| 66 | } else { | ||
| 67 | return ReadStringFromFile(std::filesystem::path{path}, type); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | #endif | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Writes a string to a file at path and returns the number of characters successfully written. | ||
| 74 | * If an file already exists at path, its contents will be erased. | ||
| 75 | * If the filesystem object at path is not a file, this function returns 0. | ||
| 76 | * | ||
| 77 | * @param path Filesystem path | ||
| 78 | * @param type File type | ||
| 79 | * | ||
| 80 | * @returns Number of characters successfully written. | ||
| 81 | */ | ||
| 82 | [[nodiscard]] size_t WriteStringToFile(const std::filesystem::path& path, FileType type, | ||
| 83 | std::string_view string); | ||
| 84 | |||
| 85 | #ifdef _WIN32 | ||
| 86 | template <typename Path> | ||
| 87 | [[nodiscard]] size_t WriteStringToFile(const Path& path, FileType type, std::string_view string) { | ||
| 88 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 89 | return WriteStringToFile(ToU8String(path), type, string); | ||
| 90 | } else { | ||
| 91 | return WriteStringToFile(std::filesystem::path{path}, type, string); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | #endif | ||
| 95 | |||
| 96 | /** | ||
| 97 | * Appends a string to a file at path and returns the number of characters successfully written. | ||
| 98 | * If a file does not exist at path, WriteStringToFile is called instead. | ||
| 99 | * If the filesystem object at path is not a file, this function returns 0. | ||
| 100 | * | ||
| 101 | * @param path Filesystem path | ||
| 102 | * @param type File type | ||
| 103 | * | ||
| 104 | * @returns Number of characters successfully written. | ||
| 105 | */ | ||
| 106 | [[nodiscard]] size_t AppendStringToFile(const std::filesystem::path& path, FileType type, | ||
| 107 | std::string_view string); | ||
| 108 | |||
| 109 | #ifdef _WIN32 | ||
| 110 | template <typename Path> | ||
| 111 | [[nodiscard]] size_t AppendStringToFile(const Path& path, FileType type, std::string_view string) { | ||
| 112 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 113 | return AppendStringToFile(ToU8String(path), type, string); | ||
| 114 | } else { | ||
| 115 | return AppendStringToFile(std::filesystem::path{path}, type, string); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | #endif | ||
| 119 | |||
| 120 | class IOFile final : NonCopyable { | ||
| 121 | public: | ||
| 122 | IOFile(); | ||
| 123 | |||
| 124 | explicit IOFile(const std::string& path, FileAccessMode mode, | ||
| 125 | FileType type = FileType::BinaryFile, | ||
| 126 | FileShareFlag flag = FileShareFlag::ShareReadOnly); | ||
| 127 | |||
| 128 | explicit IOFile(std::string_view path, FileAccessMode mode, | ||
| 129 | FileType type = FileType::BinaryFile, | ||
| 130 | FileShareFlag flag = FileShareFlag::ShareReadOnly); | ||
| 131 | |||
| 132 | /** | ||
| 133 | * An IOFile is a lightweight wrapper on C Library file operations. | ||
| 134 | * Automatically closes an open file on the destruction of an IOFile object. | ||
| 135 | * | ||
| 136 | * @param path Filesystem path | ||
| 137 | * @param mode File access mode | ||
| 138 | * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file | ||
| 139 | * @param flag (Windows only) File-share access flag, default is ShareReadOnly | ||
| 140 | */ | ||
| 141 | explicit IOFile(const std::filesystem::path& path, FileAccessMode mode, | ||
| 142 | FileType type = FileType::BinaryFile, | ||
| 143 | FileShareFlag flag = FileShareFlag::ShareReadOnly); | ||
| 144 | |||
| 145 | virtual ~IOFile(); | ||
| 146 | |||
| 147 | IOFile(IOFile&& other) noexcept; | ||
| 148 | IOFile& operator=(IOFile&& other) noexcept; | ||
| 149 | |||
| 150 | /** | ||
| 151 | * Gets the path of the file. | ||
| 152 | * | ||
| 153 | * @returns The path of the file. | ||
| 154 | */ | ||
| 155 | [[nodiscard]] std::filesystem::path GetPath() const; | ||
| 156 | |||
| 157 | /** | ||
| 158 | * Gets the access mode of the file. | ||
| 159 | * | ||
| 160 | * @returns The access mode of the file. | ||
| 161 | */ | ||
| 162 | [[nodiscard]] FileAccessMode GetAccessMode() const; | ||
| 163 | |||
| 164 | /** | ||
| 165 | * Gets the type of the file. | ||
| 166 | * | ||
| 167 | * @returns The type of the file. | ||
| 168 | */ | ||
| 169 | [[nodiscard]] FileType GetType() const; | ||
| 170 | |||
| 171 | /** | ||
| 172 | * Opens a file at path with the specified file access mode. | ||
| 173 | * This function behaves differently depending on the FileAccessMode. | ||
| 174 | * These behaviors are documented in each enum value of FileAccessMode. | ||
| 175 | * | ||
| 176 | * @param path Filesystem path | ||
| 177 | * @param mode File access mode | ||
| 178 | * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file | ||
| 179 | * @param flag (Windows only) File-share access flag, default is ShareReadOnly | ||
| 180 | */ | ||
| 181 | void Open(const std::filesystem::path& path, FileAccessMode mode, | ||
| 182 | FileType type = FileType::BinaryFile, | ||
| 183 | FileShareFlag flag = FileShareFlag::ShareReadOnly); | ||
| 184 | |||
| 185 | #ifdef _WIN32 | ||
| 186 | template <typename Path> | ||
| 187 | [[nodiscard]] void Open(const Path& path, FileAccessMode mode, | ||
| 188 | FileType type = FileType::BinaryFile, | ||
| 189 | FileShareFlag flag = FileShareFlag::ShareReadOnly) { | ||
| 190 | using ValueType = typename Path::value_type; | ||
| 191 | if constexpr (IsChar<ValueType>) { | ||
| 192 | Open(ToU8String(path), mode, type, flag); | ||
| 193 | } else { | ||
| 194 | Open(std::filesystem::path{path}, mode, type, flag); | ||
| 195 | } | ||
| 196 | } | ||
| 197 | #endif | ||
| 198 | |||
| 199 | /// Closes the file if it is opened. | ||
| 200 | void Close(); | ||
| 201 | |||
| 202 | /** | ||
| 203 | * Checks whether the file is open. | ||
| 204 | * Use this to check whether the calls to Open() or Close() succeeded. | ||
| 205 | * | ||
| 206 | * @returns True if the file is open, false otherwise. | ||
| 207 | */ | ||
| 208 | [[nodiscard]] bool IsOpen() const; | ||
| 209 | |||
| 210 | /** | ||
| 211 | * Helper function which deduces the value type of a contiguous STL container used in ReadSpan. | ||
| 212 | * If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls | ||
| 213 | * ReadObject and T must be a trivially copyable object. | ||
| 214 | * | ||
| 215 | * See ReadSpan for more details if T is a contiguous container. | ||
| 216 | * See ReadObject for more details if T is a trivially copyable object. | ||
| 217 | * | ||
| 218 | * @tparam T Contiguous container or trivially copyable object | ||
| 219 | * | ||
| 220 | * @param data Container of T::value_type data or reference to object | ||
| 221 | * | ||
| 222 | * @returns Count of T::value_type data or objects successfully read. | ||
| 223 | */ | ||
| 224 | template <typename T> | ||
| 225 | [[nodiscard]] size_t Read(T& data) const { | ||
| 226 | if constexpr (IsSTLContainer<T>) { | ||
| 227 | using ContiguousType = typename T::value_type; | ||
| 228 | static_assert(std::is_trivially_copyable_v<ContiguousType>, | ||
| 229 | "Data type must be trivially copyable."); | ||
| 230 | return ReadSpan<ContiguousType>(data); | ||
| 231 | } else { | ||
| 232 | return ReadObject(data) ? 1 : 0; | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | /** | ||
| 237 | * Helper function which deduces the value type of a contiguous STL container used in WriteSpan. | ||
| 238 | * If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls | ||
| 239 | * WriteObject and T must be a trivially copyable object. | ||
| 240 | * | ||
| 241 | * See WriteSpan for more details if T is a contiguous container. | ||
| 242 | * See WriteObject for more details if T is a trivially copyable object. | ||
| 243 | * | ||
| 244 | * @tparam T Contiguous container or trivially copyable object | ||
| 245 | * | ||
| 246 | * @param data Container of T::value_type data or const reference to object | ||
| 247 | * | ||
| 248 | * @returns Count of T::value_type data or objects successfully written. | ||
| 249 | */ | ||
| 250 | template <typename T> | ||
| 251 | [[nodiscard]] size_t Write(const T& data) const { | ||
| 252 | if constexpr (IsSTLContainer<T>) { | ||
| 253 | using ContiguousType = typename T::value_type; | ||
| 254 | static_assert(std::is_trivially_copyable_v<ContiguousType>, | ||
| 255 | "Data type must be trivially copyable."); | ||
| 256 | return WriteSpan<ContiguousType>(data); | ||
| 257 | } else { | ||
| 258 | static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); | ||
| 259 | return WriteObject(data) ? 1 : 0; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | /** | ||
| 264 | * Reads a span of T data from a file sequentially. | ||
| 265 | * This function reads from the current position of the file pointer and | ||
| 266 | * advances it by the (count of T * sizeof(T)) bytes successfully read. | ||
| 267 | * | ||
| 268 | * Failures occur when: | ||
| 269 | * - The file is not open | ||
| 270 | * - The opened file lacks read permissions | ||
| 271 | * - Attempting to read beyond the end-of-file | ||
| 272 | * | ||
| 273 | * @tparam T Data type | ||
| 274 | * | ||
| 275 | * @param data Span of T data | ||
| 276 | * | ||
| 277 | * @returns Count of T data successfully read. | ||
| 278 | */ | ||
| 279 | template <typename T> | ||
| 280 | [[nodiscard]] size_t ReadSpan(std::span<T> data) const { | ||
| 281 | static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); | ||
| 282 | |||
| 283 | if (!IsOpen()) { | ||
| 284 | return 0; | ||
| 285 | } | ||
| 286 | |||
| 287 | return std::fread(data.data(), sizeof(T), data.size(), file); | ||
| 288 | } | ||
| 289 | |||
| 290 | /** | ||
| 291 | * Writes a span of T data to a file sequentially. | ||
| 292 | * This function writes from the current position of the file pointer and | ||
| 293 | * advances it by the (count of T * sizeof(T)) bytes successfully written. | ||
| 294 | * | ||
| 295 | * Failures occur when: | ||
| 296 | * - The file is not open | ||
| 297 | * - The opened file lacks write permissions | ||
| 298 | * | ||
| 299 | * @tparam T Data type | ||
| 300 | * | ||
| 301 | * @param data Span of T data | ||
| 302 | * | ||
| 303 | * @returns Count of T data successfully written. | ||
| 304 | */ | ||
| 305 | template <typename T> | ||
| 306 | [[nodiscard]] size_t WriteSpan(std::span<const T> data) const { | ||
| 307 | static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); | ||
| 308 | |||
| 309 | if (!IsOpen()) { | ||
| 310 | return 0; | ||
| 311 | } | ||
| 312 | |||
| 313 | return std::fwrite(data.data(), sizeof(T), data.size(), file); | ||
| 314 | } | ||
| 315 | |||
| 316 | /** | ||
| 317 | * Reads a T object from a file sequentially. | ||
| 318 | * This function reads from the current position of the file pointer and | ||
| 319 | * advances it by the sizeof(T) bytes successfully read. | ||
| 320 | * | ||
| 321 | * Failures occur when: | ||
| 322 | * - The file is not open | ||
| 323 | * - The opened file lacks read permissions | ||
| 324 | * - Attempting to read beyond the end-of-file | ||
| 325 | * | ||
| 326 | * @tparam T Data type | ||
| 327 | * | ||
| 328 | * @param object Reference to object | ||
| 329 | * | ||
| 330 | * @returns True if the object is successfully read from the file, false otherwise. | ||
| 331 | */ | ||
| 332 | template <typename T> | ||
| 333 | [[nodiscard]] bool ReadObject(T& object) const { | ||
| 334 | static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); | ||
| 335 | static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object."); | ||
| 336 | |||
| 337 | if (!IsOpen()) { | ||
| 338 | return false; | ||
| 339 | } | ||
| 340 | |||
| 341 | return std::fread(&object, sizeof(T), 1, file) == 1; | ||
| 342 | } | ||
| 343 | |||
| 344 | /** | ||
| 345 | * Writes a T object to a file sequentially. | ||
| 346 | * This function writes from the current position of the file pointer and | ||
| 347 | * advances it by the sizeof(T) bytes successfully written. | ||
| 348 | * | ||
| 349 | * Failures occur when: | ||
| 350 | * - The file is not open | ||
| 351 | * - The opened file lacks write permissions | ||
| 352 | * | ||
| 353 | * @tparam T Data type | ||
| 354 | * | ||
| 355 | * @param object Const reference to object | ||
| 356 | * | ||
| 357 | * @returns True if the object is successfully written to the file, false otherwise. | ||
| 358 | */ | ||
| 359 | template <typename T> | ||
| 360 | [[nodiscard]] bool WriteObject(const T& object) const { | ||
| 361 | static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); | ||
| 362 | static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object."); | ||
| 363 | |||
| 364 | if (!IsOpen()) { | ||
| 365 | return false; | ||
| 366 | } | ||
| 367 | |||
| 368 | return std::fwrite(&object, sizeof(T), 1, file) == 1; | ||
| 369 | } | ||
| 370 | |||
| 371 | /** | ||
| 372 | * Specialized function to read a string of a given length from a file sequentially. | ||
| 373 | * This function writes from the current position of the file pointer and | ||
| 374 | * advances it by the number of characters successfully read. | ||
| 375 | * The size of the returned string may not match length if not all bytes are successfully read. | ||
| 376 | * | ||
| 377 | * @param length Length of the string | ||
| 378 | * | ||
| 379 | * @returns A string read from the file. | ||
| 380 | */ | ||
| 381 | [[nodiscard]] std::string ReadString(size_t length) const; | ||
| 382 | |||
| 383 | /** | ||
| 384 | * Specialized function to write a string to a file sequentially. | ||
| 385 | * This function writes from the current position of the file pointer and | ||
| 386 | * advances it by the number of characters successfully written. | ||
| 387 | * | ||
| 388 | * @param string Span of const char backed std::string or std::string_view | ||
| 389 | * | ||
| 390 | * @returns Number of characters successfully written. | ||
| 391 | */ | ||
| 392 | [[nodiscard]] size_t WriteString(std::span<const char> string) const; | ||
| 393 | |||
| 394 | /** | ||
| 395 | * Flushes any unwritten buffered data into the file. | ||
| 396 | * | ||
| 397 | * @returns True if the flush was successful, false otherwise. | ||
| 398 | */ | ||
| 399 | [[nodiscard]] bool Flush() const; | ||
| 400 | |||
| 401 | /** | ||
| 402 | * Resizes the file to a given size. | ||
| 403 | * If the file is resized to a smaller size, the remainder of the file is discarded. | ||
| 404 | * If the file is resized to a larger size, the new area appears as if zero-filled. | ||
| 405 | * | ||
| 406 | * Failures occur when: | ||
| 407 | * - The file is not open | ||
| 408 | * | ||
| 409 | * @param size File size in bytes | ||
| 410 | * | ||
| 411 | * @returns True if the file resize succeeded, false otherwise. | ||
| 412 | */ | ||
| 413 | [[nodiscard]] bool SetSize(u64 size) const; | ||
| 414 | |||
| 415 | /** | ||
| 416 | * Gets the size of the file. | ||
| 417 | * | ||
| 418 | * Failures occur when: | ||
| 419 | * - The file is not open | ||
| 420 | * | ||
| 421 | * @returns The file size in bytes of the file. Returns 0 on failure. | ||
| 422 | */ | ||
| 423 | [[nodiscard]] u64 GetSize() const; | ||
| 424 | |||
| 425 | /** | ||
| 426 | * Moves the current position of the file pointer with the specified offset and seek origin. | ||
| 427 | * | ||
| 428 | * @param offset Offset from seek origin | ||
| 429 | * @param origin Seek origin | ||
| 430 | * | ||
| 431 | * @returns True if the file pointer has moved to the specified offset, false otherwise. | ||
| 432 | */ | ||
| 433 | [[nodiscard]] bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const; | ||
| 434 | |||
| 435 | /** | ||
| 436 | * Gets the current position of the file pointer. | ||
| 437 | * | ||
| 438 | * @returns The current position of the file pointer. | ||
| 439 | */ | ||
| 440 | [[nodiscard]] s64 Tell() const; | ||
| 441 | |||
| 442 | private: | ||
| 443 | std::filesystem::path file_path; | ||
| 444 | FileAccessMode file_access_mode; | ||
| 445 | FileType file_type; | ||
| 446 | |||
| 447 | std::FILE* file = nullptr; | ||
| 448 | }; | ||
| 449 | |||
| 450 | } // namespace Common::FS | ||
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp new file mode 100644 index 000000000..d492480d9 --- /dev/null +++ b/src/common/fs/fs.cpp | |||
| @@ -0,0 +1,610 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "common/fs/file.h" | ||
| 6 | #include "common/fs/fs.h" | ||
| 7 | #include "common/fs/path_util.h" | ||
| 8 | #include "common/logging/log.h" | ||
| 9 | |||
| 10 | namespace Common::FS { | ||
| 11 | |||
| 12 | namespace fs = std::filesystem; | ||
| 13 | |||
| 14 | // File Operations | ||
| 15 | |||
| 16 | bool NewFile(const fs::path& path, u64 size) { | ||
| 17 | if (!ValidatePath(path)) { | ||
| 18 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 19 | return false; | ||
| 20 | } | ||
| 21 | |||
| 22 | if (!Exists(path.parent_path())) { | ||
| 23 | LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist", | ||
| 24 | PathToUTF8String(path)); | ||
| 25 | return false; | ||
| 26 | } | ||
| 27 | |||
| 28 | if (Exists(path)) { | ||
| 29 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} exists", PathToUTF8String(path)); | ||
| 30 | return false; | ||
| 31 | } | ||
| 32 | |||
| 33 | IOFile io_file{path, FileAccessMode::Write}; | ||
| 34 | |||
| 35 | if (!io_file.IsOpen()) { | ||
| 36 | LOG_ERROR(Common_Filesystem, "Failed to create a file at path={}", PathToUTF8String(path)); | ||
| 37 | return false; | ||
| 38 | } | ||
| 39 | |||
| 40 | if (!io_file.SetSize(size)) { | ||
| 41 | LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={} to size={}", | ||
| 42 | PathToUTF8String(path), size); | ||
| 43 | return false; | ||
| 44 | } | ||
| 45 | |||
| 46 | io_file.Close(); | ||
| 47 | |||
| 48 | LOG_DEBUG(Common_Filesystem, "Successfully created a file at path={} with size={}", | ||
| 49 | PathToUTF8String(path), size); | ||
| 50 | |||
| 51 | return true; | ||
| 52 | } | ||
| 53 | |||
| 54 | bool RemoveFile(const fs::path& path) { | ||
| 55 | if (!ValidatePath(path)) { | ||
| 56 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 57 | return false; | ||
| 58 | } | ||
| 59 | |||
| 60 | if (!Exists(path)) { | ||
| 61 | LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", | ||
| 62 | PathToUTF8String(path)); | ||
| 63 | return true; | ||
| 64 | } | ||
| 65 | |||
| 66 | if (!IsFile(path)) { | ||
| 67 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file", | ||
| 68 | PathToUTF8String(path)); | ||
| 69 | return false; | ||
| 70 | } | ||
| 71 | |||
| 72 | std::error_code ec; | ||
| 73 | |||
| 74 | fs::remove(path, ec); | ||
| 75 | |||
| 76 | if (ec) { | ||
| 77 | LOG_ERROR(Common_Filesystem, "Failed to remove the file at path={}, ec_message={}", | ||
| 78 | PathToUTF8String(path), ec.message()); | ||
| 79 | return false; | ||
| 80 | } | ||
| 81 | |||
| 82 | LOG_DEBUG(Common_Filesystem, "Successfully removed the file at path={}", | ||
| 83 | PathToUTF8String(path)); | ||
| 84 | |||
| 85 | return true; | ||
| 86 | } | ||
| 87 | |||
| 88 | bool RenameFile(const fs::path& old_path, const fs::path& new_path) { | ||
| 89 | if (!ValidatePath(old_path) || !ValidatePath(new_path)) { | ||
| 90 | LOG_ERROR(Common_Filesystem, | ||
| 91 | "One or both input path(s) is not valid, old_path={}, new_path={}", | ||
| 92 | PathToUTF8String(old_path), PathToUTF8String(new_path)); | ||
| 93 | return false; | ||
| 94 | } | ||
| 95 | |||
| 96 | if (!Exists(old_path)) { | ||
| 97 | LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} does not exist", | ||
| 98 | PathToUTF8String(old_path)); | ||
| 99 | return false; | ||
| 100 | } | ||
| 101 | |||
| 102 | if (!IsFile(old_path)) { | ||
| 103 | LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} is not a file", | ||
| 104 | PathToUTF8String(old_path)); | ||
| 105 | return false; | ||
| 106 | } | ||
| 107 | |||
| 108 | if (Exists(new_path)) { | ||
| 109 | LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} exists", | ||
| 110 | PathToUTF8String(new_path)); | ||
| 111 | return false; | ||
| 112 | } | ||
| 113 | |||
| 114 | std::error_code ec; | ||
| 115 | |||
| 116 | fs::rename(old_path, new_path, ec); | ||
| 117 | |||
| 118 | if (ec) { | ||
| 119 | LOG_ERROR(Common_Filesystem, | ||
| 120 | "Failed to rename the file from old_path={} to new_path={}, ec_message={}", | ||
| 121 | PathToUTF8String(old_path), PathToUTF8String(new_path), ec.message()); | ||
| 122 | return false; | ||
| 123 | } | ||
| 124 | |||
| 125 | LOG_DEBUG(Common_Filesystem, "Successfully renamed the file from old_path={} to new_path={}", | ||
| 126 | PathToUTF8String(old_path), PathToUTF8String(new_path)); | ||
| 127 | |||
| 128 | return true; | ||
| 129 | } | ||
| 130 | |||
| 131 | std::shared_ptr<IOFile> FileOpen(const fs::path& path, FileAccessMode mode, FileType type, | ||
| 132 | FileShareFlag flag) { | ||
| 133 | if (!ValidatePath(path)) { | ||
| 134 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 135 | return nullptr; | ||
| 136 | } | ||
| 137 | |||
| 138 | if (!IsFile(path)) { | ||
| 139 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file", | ||
| 140 | PathToUTF8String(path)); | ||
| 141 | return nullptr; | ||
| 142 | } | ||
| 143 | |||
| 144 | auto io_file = std::make_shared<IOFile>(path, mode, type, flag); | ||
| 145 | |||
| 146 | if (!io_file->IsOpen()) { | ||
| 147 | io_file.reset(); | ||
| 148 | |||
| 149 | LOG_ERROR(Common_Filesystem, | ||
| 150 | "Failed to open the file at path={} with mode={}, type={}, flag={}", | ||
| 151 | PathToUTF8String(path), mode, type, flag); | ||
| 152 | |||
| 153 | return nullptr; | ||
| 154 | } | ||
| 155 | |||
| 156 | LOG_DEBUG(Common_Filesystem, | ||
| 157 | "Successfully opened the file at path={} with mode={}, type={}, flag={}", | ||
| 158 | PathToUTF8String(path), mode, type, flag); | ||
| 159 | |||
| 160 | return io_file; | ||
| 161 | } | ||
| 162 | |||
| 163 | // Directory Operations | ||
| 164 | |||
| 165 | bool CreateDir(const fs::path& path) { | ||
| 166 | if (!ValidatePath(path)) { | ||
| 167 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 168 | return false; | ||
| 169 | } | ||
| 170 | |||
| 171 | if (!Exists(path.parent_path())) { | ||
| 172 | LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist", | ||
| 173 | PathToUTF8String(path)); | ||
| 174 | return false; | ||
| 175 | } | ||
| 176 | |||
| 177 | if (IsDir(path)) { | ||
| 178 | LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory", | ||
| 179 | PathToUTF8String(path)); | ||
| 180 | return true; | ||
| 181 | } | ||
| 182 | |||
| 183 | std::error_code ec; | ||
| 184 | |||
| 185 | fs::create_directory(path, ec); | ||
| 186 | |||
| 187 | if (ec) { | ||
| 188 | LOG_ERROR(Common_Filesystem, "Failed to create the directory at path={}, ec_message={}", | ||
| 189 | PathToUTF8String(path), ec.message()); | ||
| 190 | return false; | ||
| 191 | } | ||
| 192 | |||
| 193 | LOG_DEBUG(Common_Filesystem, "Successfully created the directory at path={}", | ||
| 194 | PathToUTF8String(path)); | ||
| 195 | |||
| 196 | return true; | ||
| 197 | } | ||
| 198 | |||
| 199 | bool CreateDirs(const fs::path& path) { | ||
| 200 | if (!ValidatePath(path)) { | ||
| 201 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 202 | return false; | ||
| 203 | } | ||
| 204 | |||
| 205 | if (IsDir(path)) { | ||
| 206 | LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory", | ||
| 207 | PathToUTF8String(path)); | ||
| 208 | return true; | ||
| 209 | } | ||
| 210 | |||
| 211 | std::error_code ec; | ||
| 212 | |||
| 213 | fs::create_directories(path, ec); | ||
| 214 | |||
| 215 | if (ec) { | ||
| 216 | LOG_ERROR(Common_Filesystem, "Failed to create the directories at path={}, ec_message={}", | ||
| 217 | PathToUTF8String(path), ec.message()); | ||
| 218 | return false; | ||
| 219 | } | ||
| 220 | |||
| 221 | LOG_DEBUG(Common_Filesystem, "Successfully created the directories at path={}", | ||
| 222 | PathToUTF8String(path)); | ||
| 223 | |||
| 224 | return true; | ||
| 225 | } | ||
| 226 | |||
| 227 | bool CreateParentDir(const fs::path& path) { | ||
| 228 | return CreateDir(path.parent_path()); | ||
| 229 | } | ||
| 230 | |||
| 231 | bool CreateParentDirs(const fs::path& path) { | ||
| 232 | return CreateDirs(path.parent_path()); | ||
| 233 | } | ||
| 234 | |||
| 235 | bool RemoveDir(const fs::path& path) { | ||
| 236 | if (!ValidatePath(path)) { | ||
| 237 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 238 | return false; | ||
| 239 | } | ||
| 240 | |||
| 241 | if (!Exists(path)) { | ||
| 242 | LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", | ||
| 243 | PathToUTF8String(path)); | ||
| 244 | return true; | ||
| 245 | } | ||
| 246 | |||
| 247 | if (!IsDir(path)) { | ||
| 248 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", | ||
| 249 | PathToUTF8String(path)); | ||
| 250 | return false; | ||
| 251 | } | ||
| 252 | |||
| 253 | std::error_code ec; | ||
| 254 | |||
| 255 | fs::remove(path, ec); | ||
| 256 | |||
| 257 | if (ec) { | ||
| 258 | LOG_ERROR(Common_Filesystem, "Failed to remove the directory at path={}, ec_message={}", | ||
| 259 | PathToUTF8String(path), ec.message()); | ||
| 260 | return false; | ||
| 261 | } | ||
| 262 | |||
| 263 | LOG_DEBUG(Common_Filesystem, "Successfully removed the directory at path={}", | ||
| 264 | PathToUTF8String(path)); | ||
| 265 | |||
| 266 | return true; | ||
| 267 | } | ||
| 268 | |||
| 269 | bool RemoveDirRecursively(const fs::path& path) { | ||
| 270 | if (!ValidatePath(path)) { | ||
| 271 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 272 | return false; | ||
| 273 | } | ||
| 274 | |||
| 275 | if (!Exists(path)) { | ||
| 276 | LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", | ||
| 277 | PathToUTF8String(path)); | ||
| 278 | return true; | ||
| 279 | } | ||
| 280 | |||
| 281 | if (!IsDir(path)) { | ||
| 282 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", | ||
| 283 | PathToUTF8String(path)); | ||
| 284 | return false; | ||
| 285 | } | ||
| 286 | |||
| 287 | std::error_code ec; | ||
| 288 | |||
| 289 | fs::remove_all(path, ec); | ||
| 290 | |||
| 291 | if (ec) { | ||
| 292 | LOG_ERROR(Common_Filesystem, | ||
| 293 | "Failed to remove the directory and its contents at path={}, ec_message={}", | ||
| 294 | PathToUTF8String(path), ec.message()); | ||
| 295 | return false; | ||
| 296 | } | ||
| 297 | |||
| 298 | LOG_DEBUG(Common_Filesystem, "Successfully removed the directory and its contents at path={}", | ||
| 299 | PathToUTF8String(path)); | ||
| 300 | |||
| 301 | return true; | ||
| 302 | } | ||
| 303 | |||
| 304 | bool RemoveDirContentsRecursively(const fs::path& path) { | ||
| 305 | if (!ValidatePath(path)) { | ||
| 306 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 307 | return false; | ||
| 308 | } | ||
| 309 | |||
| 310 | if (!Exists(path)) { | ||
| 311 | LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", | ||
| 312 | PathToUTF8String(path)); | ||
| 313 | return true; | ||
| 314 | } | ||
| 315 | |||
| 316 | if (!IsDir(path)) { | ||
| 317 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", | ||
| 318 | PathToUTF8String(path)); | ||
| 319 | return false; | ||
| 320 | } | ||
| 321 | |||
| 322 | std::error_code ec; | ||
| 323 | |||
| 324 | for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { | ||
| 325 | if (ec) { | ||
| 326 | LOG_ERROR(Common_Filesystem, | ||
| 327 | "Failed to completely enumerate the directory at path={}, ec_message={}", | ||
| 328 | PathToUTF8String(path), ec.message()); | ||
| 329 | break; | ||
| 330 | } | ||
| 331 | |||
| 332 | fs::remove(entry.path(), ec); | ||
| 333 | |||
| 334 | if (ec) { | ||
| 335 | LOG_ERROR(Common_Filesystem, | ||
| 336 | "Failed to remove the filesystem object at path={}, ec_message={}", | ||
| 337 | PathToUTF8String(entry.path()), ec.message()); | ||
| 338 | break; | ||
| 339 | } | ||
| 340 | } | ||
| 341 | |||
| 342 | if (ec) { | ||
| 343 | LOG_ERROR(Common_Filesystem, | ||
| 344 | "Failed to remove all the contents of the directory at path={}, ec_message={}", | ||
| 345 | PathToUTF8String(path), ec.message()); | ||
| 346 | return false; | ||
| 347 | } | ||
| 348 | |||
| 349 | LOG_DEBUG(Common_Filesystem, | ||
| 350 | "Successfully removed all the contents of the directory at path={}", | ||
| 351 | PathToUTF8String(path)); | ||
| 352 | |||
| 353 | return true; | ||
| 354 | } | ||
| 355 | |||
| 356 | bool RenameDir(const fs::path& old_path, const fs::path& new_path) { | ||
| 357 | if (!ValidatePath(old_path) || !ValidatePath(new_path)) { | ||
| 358 | LOG_ERROR(Common_Filesystem, | ||
| 359 | "One or both input path(s) is not valid, old_path={}, new_path={}", | ||
| 360 | PathToUTF8String(old_path), PathToUTF8String(new_path)); | ||
| 361 | return false; | ||
| 362 | } | ||
| 363 | |||
| 364 | if (!Exists(old_path)) { | ||
| 365 | LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} does not exist", | ||
| 366 | PathToUTF8String(old_path)); | ||
| 367 | return false; | ||
| 368 | } | ||
| 369 | |||
| 370 | if (!IsDir(old_path)) { | ||
| 371 | LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} is not a directory", | ||
| 372 | PathToUTF8String(old_path)); | ||
| 373 | return false; | ||
| 374 | } | ||
| 375 | |||
| 376 | if (Exists(new_path)) { | ||
| 377 | LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} exists", | ||
| 378 | PathToUTF8String(new_path)); | ||
| 379 | return false; | ||
| 380 | } | ||
| 381 | |||
| 382 | std::error_code ec; | ||
| 383 | |||
| 384 | fs::rename(old_path, new_path, ec); | ||
| 385 | |||
| 386 | if (ec) { | ||
| 387 | LOG_ERROR(Common_Filesystem, | ||
| 388 | "Failed to rename the file from old_path={} to new_path={}, ec_message={}", | ||
| 389 | PathToUTF8String(old_path), PathToUTF8String(new_path), ec.message()); | ||
| 390 | return false; | ||
| 391 | } | ||
| 392 | |||
| 393 | LOG_DEBUG(Common_Filesystem, "Successfully renamed the file from old_path={} to new_path={}", | ||
| 394 | PathToUTF8String(old_path), PathToUTF8String(new_path)); | ||
| 395 | |||
| 396 | return true; | ||
| 397 | } | ||
| 398 | |||
| 399 | void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback, | ||
| 400 | DirEntryFilter filter) { | ||
| 401 | if (!ValidatePath(path)) { | ||
| 402 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 403 | return; | ||
| 404 | } | ||
| 405 | |||
| 406 | if (!Exists(path)) { | ||
| 407 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist", | ||
| 408 | PathToUTF8String(path)); | ||
| 409 | return; | ||
| 410 | } | ||
| 411 | |||
| 412 | if (!IsDir(path)) { | ||
| 413 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", | ||
| 414 | PathToUTF8String(path)); | ||
| 415 | return; | ||
| 416 | } | ||
| 417 | |||
| 418 | bool callback_error = false; | ||
| 419 | |||
| 420 | std::error_code ec; | ||
| 421 | |||
| 422 | for (const auto& entry : fs::directory_iterator(path, ec)) { | ||
| 423 | if (ec) { | ||
| 424 | break; | ||
| 425 | } | ||
| 426 | |||
| 427 | if (True(filter & DirEntryFilter::File) && | ||
| 428 | entry.status().type() == fs::file_type::regular) { | ||
| 429 | if (!callback(entry.path())) { | ||
| 430 | callback_error = true; | ||
| 431 | break; | ||
| 432 | } | ||
| 433 | } | ||
| 434 | |||
| 435 | if (True(filter & DirEntryFilter::Directory) && | ||
| 436 | entry.status().type() == fs::file_type::directory) { | ||
| 437 | if (!callback(entry.path())) { | ||
| 438 | callback_error = true; | ||
| 439 | break; | ||
| 440 | } | ||
| 441 | } | ||
| 442 | } | ||
| 443 | |||
| 444 | if (callback_error || ec) { | ||
| 445 | LOG_ERROR(Common_Filesystem, | ||
| 446 | "Failed to visit all the directory entries of path={}, ec_message={}", | ||
| 447 | PathToUTF8String(path), ec.message()); | ||
| 448 | return; | ||
| 449 | } | ||
| 450 | |||
| 451 | LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}", | ||
| 452 | PathToUTF8String(path)); | ||
| 453 | } | ||
| 454 | |||
| 455 | void IterateDirEntriesRecursively(const std::filesystem::path& path, | ||
| 456 | const DirEntryCallable& callback, DirEntryFilter filter) { | ||
| 457 | if (!ValidatePath(path)) { | ||
| 458 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 459 | return; | ||
| 460 | } | ||
| 461 | |||
| 462 | if (!Exists(path)) { | ||
| 463 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist", | ||
| 464 | PathToUTF8String(path)); | ||
| 465 | return; | ||
| 466 | } | ||
| 467 | |||
| 468 | if (!IsDir(path)) { | ||
| 469 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", | ||
| 470 | PathToUTF8String(path)); | ||
| 471 | return; | ||
| 472 | } | ||
| 473 | |||
| 474 | bool callback_error = false; | ||
| 475 | |||
| 476 | std::error_code ec; | ||
| 477 | |||
| 478 | for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { | ||
| 479 | if (ec) { | ||
| 480 | break; | ||
| 481 | } | ||
| 482 | |||
| 483 | if (True(filter & DirEntryFilter::File) && | ||
| 484 | entry.status().type() == fs::file_type::regular) { | ||
| 485 | if (!callback(entry.path())) { | ||
| 486 | callback_error = true; | ||
| 487 | break; | ||
| 488 | } | ||
| 489 | } | ||
| 490 | |||
| 491 | if (True(filter & DirEntryFilter::Directory) && | ||
| 492 | entry.status().type() == fs::file_type::directory) { | ||
| 493 | if (!callback(entry.path())) { | ||
| 494 | callback_error = true; | ||
| 495 | break; | ||
| 496 | } | ||
| 497 | } | ||
| 498 | } | ||
| 499 | |||
| 500 | if (callback_error || ec) { | ||
| 501 | LOG_ERROR(Common_Filesystem, | ||
| 502 | "Failed to visit all the directory entries of path={}, ec_message={}", | ||
| 503 | PathToUTF8String(path), ec.message()); | ||
| 504 | return; | ||
| 505 | } | ||
| 506 | |||
| 507 | LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}", | ||
| 508 | PathToUTF8String(path)); | ||
| 509 | } | ||
| 510 | |||
| 511 | // Generic Filesystem Operations | ||
| 512 | |||
| 513 | bool Exists(const fs::path& path) { | ||
| 514 | return fs::exists(path); | ||
| 515 | } | ||
| 516 | |||
| 517 | bool IsFile(const fs::path& path) { | ||
| 518 | return fs::is_regular_file(path); | ||
| 519 | } | ||
| 520 | |||
| 521 | bool IsDir(const fs::path& path) { | ||
| 522 | return fs::is_directory(path); | ||
| 523 | } | ||
| 524 | |||
| 525 | fs::path GetCurrentDir() { | ||
| 526 | std::error_code ec; | ||
| 527 | |||
| 528 | const auto current_path = fs::current_path(ec); | ||
| 529 | |||
| 530 | if (ec) { | ||
| 531 | LOG_ERROR(Common_Filesystem, "Failed to get the current path, ec_message={}", ec.message()); | ||
| 532 | return {}; | ||
| 533 | } | ||
| 534 | |||
| 535 | return current_path; | ||
| 536 | } | ||
| 537 | |||
| 538 | bool SetCurrentDir(const fs::path& path) { | ||
| 539 | std::error_code ec; | ||
| 540 | |||
| 541 | fs::current_path(path, ec); | ||
| 542 | |||
| 543 | if (ec) { | ||
| 544 | LOG_ERROR(Common_Filesystem, "Failed to set the current path to path={}, ec_message={}", | ||
| 545 | PathToUTF8String(path), ec.message()); | ||
| 546 | return false; | ||
| 547 | } | ||
| 548 | |||
| 549 | return true; | ||
| 550 | } | ||
| 551 | |||
| 552 | fs::file_type GetEntryType(const fs::path& path) { | ||
| 553 | std::error_code ec; | ||
| 554 | |||
| 555 | const auto file_status = fs::status(path, ec); | ||
| 556 | |||
| 557 | if (ec) { | ||
| 558 | LOG_ERROR(Common_Filesystem, "Failed to retrieve the entry type of path={}, ec_message={}", | ||
| 559 | PathToUTF8String(path), ec.message()); | ||
| 560 | return fs::file_type::not_found; | ||
| 561 | } | ||
| 562 | |||
| 563 | return file_status.type(); | ||
| 564 | } | ||
| 565 | |||
| 566 | u64 GetSize(const fs::path& path) { | ||
| 567 | std::error_code ec; | ||
| 568 | |||
| 569 | const auto file_size = fs::file_size(path, ec); | ||
| 570 | |||
| 571 | if (ec) { | ||
| 572 | LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", | ||
| 573 | PathToUTF8String(path), ec.message()); | ||
| 574 | return 0; | ||
| 575 | } | ||
| 576 | |||
| 577 | return file_size; | ||
| 578 | } | ||
| 579 | |||
| 580 | u64 GetFreeSpaceSize(const fs::path& path) { | ||
| 581 | std::error_code ec; | ||
| 582 | |||
| 583 | const auto space_info = fs::space(path, ec); | ||
| 584 | |||
| 585 | if (ec) { | ||
| 586 | LOG_ERROR(Common_Filesystem, | ||
| 587 | "Failed to retrieve the available free space of path={}, ec_message={}", | ||
| 588 | PathToUTF8String(path), ec.message()); | ||
| 589 | return 0; | ||
| 590 | } | ||
| 591 | |||
| 592 | return space_info.free; | ||
| 593 | } | ||
| 594 | |||
| 595 | u64 GetTotalSpaceSize(const fs::path& path) { | ||
| 596 | std::error_code ec; | ||
| 597 | |||
| 598 | const auto space_info = fs::space(path, ec); | ||
| 599 | |||
| 600 | if (ec) { | ||
| 601 | LOG_ERROR(Common_Filesystem, | ||
| 602 | "Failed to retrieve the total capacity of path={}, ec_message={}", | ||
| 603 | PathToUTF8String(path), ec.message()); | ||
| 604 | return 0; | ||
| 605 | } | ||
| 606 | |||
| 607 | return space_info.capacity; | ||
| 608 | } | ||
| 609 | |||
| 610 | } // namespace Common::FS | ||
diff --git a/src/common/fs/fs.h b/src/common/fs/fs.h new file mode 100644 index 000000000..f6f256349 --- /dev/null +++ b/src/common/fs/fs.h | |||
| @@ -0,0 +1,582 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <filesystem> | ||
| 8 | #include <memory> | ||
| 9 | |||
| 10 | #include "common/fs/fs_types.h" | ||
| 11 | #include "common/fs/fs_util.h" | ||
| 12 | |||
| 13 | namespace Common::FS { | ||
| 14 | |||
| 15 | class IOFile; | ||
| 16 | |||
| 17 | // File Operations | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Creates a new file at path with the specified size. | ||
| 21 | * | ||
| 22 | * Failures occur when: | ||
| 23 | * - Input path is not valid | ||
| 24 | * - The input path's parent directory does not exist | ||
| 25 | * - Filesystem object at path exists | ||
| 26 | * - Filesystem at path is read only | ||
| 27 | * | ||
| 28 | * @param path Filesystem path | ||
| 29 | * @param size File size | ||
| 30 | * | ||
| 31 | * @returns True if the file creation succeeds, false otherwise. | ||
| 32 | */ | ||
| 33 | [[nodiscard]] bool NewFile(const std::filesystem::path& path, u64 size = 0); | ||
| 34 | |||
| 35 | #ifdef _WIN32 | ||
| 36 | template <typename Path> | ||
| 37 | [[nodiscard]] bool NewFile(const Path& path, u64 size = 0) { | ||
| 38 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 39 | return NewFile(ToU8String(path), size); | ||
| 40 | } else { | ||
| 41 | return NewFile(std::filesystem::path{path}, size); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | #endif | ||
| 45 | |||
| 46 | /** | ||
| 47 | * Removes a file at path. | ||
| 48 | * | ||
| 49 | * Failures occur when: | ||
| 50 | * - Input path is not valid | ||
| 51 | * - Filesystem object at path is not a file | ||
| 52 | * - Filesystem at path is read only | ||
| 53 | * | ||
| 54 | * @param path Filesystem path | ||
| 55 | * | ||
| 56 | * @returns True if file removal succeeds or file does not exist, false otherwise. | ||
| 57 | */ | ||
| 58 | [[nodiscard]] bool RemoveFile(const std::filesystem::path& path); | ||
| 59 | |||
| 60 | #ifdef _WIN32 | ||
| 61 | template <typename Path> | ||
| 62 | [[nodiscard]] bool RemoveFile(const Path& path) { | ||
| 63 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 64 | return RemoveFile(ToU8String(path)); | ||
| 65 | } else { | ||
| 66 | return RemoveFile(std::filesystem::path{path}); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | #endif | ||
| 70 | |||
| 71 | /** | ||
| 72 | * Renames a file from old_path to new_path. | ||
| 73 | * | ||
| 74 | * Failures occur when: | ||
| 75 | * - One or both input path(s) is not valid | ||
| 76 | * - Filesystem object at old_path does not exist | ||
| 77 | * - Filesystem object at old_path is not a file | ||
| 78 | * - Filesystem object at new_path exists | ||
| 79 | * - Filesystem at either path is read only | ||
| 80 | * | ||
| 81 | * @param old_path Old filesystem path | ||
| 82 | * @param new_path New filesystem path | ||
| 83 | * | ||
| 84 | * @returns True if file rename succeeds, false otherwise. | ||
| 85 | */ | ||
| 86 | [[nodiscard]] bool RenameFile(const std::filesystem::path& old_path, | ||
| 87 | const std::filesystem::path& new_path); | ||
| 88 | |||
| 89 | #ifdef _WIN32 | ||
| 90 | template <typename Path1, typename Path2> | ||
| 91 | [[nodiscard]] bool RenameFile(const Path1& old_path, const Path2& new_path) { | ||
| 92 | using ValueType1 = typename Path1::value_type; | ||
| 93 | using ValueType2 = typename Path2::value_type; | ||
| 94 | if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 95 | return RenameFile(ToU8String(old_path), ToU8String(new_path)); | ||
| 96 | } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { | ||
| 97 | return RenameFile(ToU8String(old_path), new_path); | ||
| 98 | } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 99 | return RenameFile(old_path, ToU8String(new_path)); | ||
| 100 | } else { | ||
| 101 | return RenameFile(std::filesystem::path{old_path}, std::filesystem::path{new_path}); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | #endif | ||
| 105 | |||
| 106 | /** | ||
| 107 | * Opens a file at path with the specified file access mode. | ||
| 108 | * This function behaves differently depending on the FileAccessMode. | ||
| 109 | * These behaviors are documented in each enum value of FileAccessMode. | ||
| 110 | * | ||
| 111 | * Failures occur when: | ||
| 112 | * - Input path is not valid | ||
| 113 | * - Filesystem object at path is not a file | ||
| 114 | * - The file is not opened | ||
| 115 | * | ||
| 116 | * @param path Filesystem path | ||
| 117 | * @param mode File access mode | ||
| 118 | * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file | ||
| 119 | * @param flag (Windows only) File-share access flag, default is ShareReadOnly | ||
| 120 | * | ||
| 121 | * @returns A shared pointer to the opened file. Returns nullptr on failure. | ||
| 122 | */ | ||
| 123 | [[nodiscard]] std::shared_ptr<IOFile> FileOpen(const std::filesystem::path& path, | ||
| 124 | FileAccessMode mode, | ||
| 125 | FileType type = FileType::BinaryFile, | ||
| 126 | FileShareFlag flag = FileShareFlag::ShareReadOnly); | ||
| 127 | |||
| 128 | #ifdef _WIN32 | ||
| 129 | template <typename Path> | ||
| 130 | [[nodiscard]] std::shared_ptr<IOFile> FileOpen(const Path& path, FileAccessMode mode, | ||
| 131 | FileType type = FileType::BinaryFile, | ||
| 132 | FileShareFlag flag = FileShareFlag::ShareReadOnly) { | ||
| 133 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 134 | return FileOpen(ToU8String(path), mode, type, flag); | ||
| 135 | } else { | ||
| 136 | return FileOpen(std::filesystem::path{path}, mode, type, flag); | ||
| 137 | } | ||
| 138 | } | ||
| 139 | #endif | ||
| 140 | |||
| 141 | // Directory Operations | ||
| 142 | |||
| 143 | /** | ||
| 144 | * Creates a directory at path. | ||
| 145 | * Note that this function will *always* assume that the input path is a directory. For example, | ||
| 146 | * if the input path is /path/to/directory/file.txt, it will create a directory called "file.txt". | ||
| 147 | * If you intend to create the parent directory of a file, use CreateParentDir instead. | ||
| 148 | * | ||
| 149 | * Failures occur when: | ||
| 150 | * - Input path is not valid | ||
| 151 | * - The input path's parent directory does not exist | ||
| 152 | * - Filesystem at path is read only | ||
| 153 | * | ||
| 154 | * @param path Filesystem path | ||
| 155 | * | ||
| 156 | * @returns True if directory creation succeeds or directory already exists, false otherwise. | ||
| 157 | */ | ||
| 158 | [[nodiscard]] bool CreateDir(const std::filesystem::path& path); | ||
| 159 | |||
| 160 | #ifdef _WIN32 | ||
| 161 | template <typename Path> | ||
| 162 | [[nodiscard]] bool CreateDir(const Path& path) { | ||
| 163 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 164 | return CreateDir(ToU8String(path)); | ||
| 165 | } else { | ||
| 166 | return CreateDir(std::filesystem::path{path}); | ||
| 167 | } | ||
| 168 | } | ||
| 169 | #endif | ||
| 170 | |||
| 171 | /** | ||
| 172 | * Recursively creates a directory at path. | ||
| 173 | * Note that this function will *always* assume that the input path is a directory. For example, | ||
| 174 | * if the input path is /path/to/directory/file.txt, it will create a directory called "file.txt". | ||
| 175 | * If you intend to create the parent directory of a file, use CreateParentDirs instead. | ||
| 176 | * Unlike CreateDir, this creates all of input path's parent directories if they do not exist. | ||
| 177 | * | ||
| 178 | * Failures occur when: | ||
| 179 | * - Input path is not valid | ||
| 180 | * - Filesystem at path is read only | ||
| 181 | * | ||
| 182 | * @param path Filesystem path | ||
| 183 | * | ||
| 184 | * @returns True if directory creation succeeds or directory already exists, false otherwise. | ||
| 185 | */ | ||
| 186 | [[nodiscard]] bool CreateDirs(const std::filesystem::path& path); | ||
| 187 | |||
| 188 | #ifdef _WIN32 | ||
| 189 | template <typename Path> | ||
| 190 | [[nodiscard]] bool CreateDirs(const Path& path) { | ||
| 191 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 192 | return CreateDirs(ToU8String(path)); | ||
| 193 | } else { | ||
| 194 | return CreateDirs(std::filesystem::path{path}); | ||
| 195 | } | ||
| 196 | } | ||
| 197 | #endif | ||
| 198 | |||
| 199 | /** | ||
| 200 | * Creates the parent directory of a given path. | ||
| 201 | * This function calls CreateDir(path.parent_path()), see CreateDir for more details. | ||
| 202 | * | ||
| 203 | * @param path Filesystem path | ||
| 204 | * | ||
| 205 | * @returns True if directory creation succeeds or directory already exists, false otherwise. | ||
| 206 | */ | ||
| 207 | [[nodiscard]] bool CreateParentDir(const std::filesystem::path& path); | ||
| 208 | |||
| 209 | #ifdef _WIN32 | ||
| 210 | template <typename Path> | ||
| 211 | [[nodiscard]] bool CreateParentDir(const Path& path) { | ||
| 212 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 213 | return CreateParentDir(ToU8String(path)); | ||
| 214 | } else { | ||
| 215 | return CreateParentDir(std::filesystem::path{path}); | ||
| 216 | } | ||
| 217 | } | ||
| 218 | #endif | ||
| 219 | |||
| 220 | /** | ||
| 221 | * Recursively creates the parent directory of a given path. | ||
| 222 | * This function calls CreateDirs(path.parent_path()), see CreateDirs for more details. | ||
| 223 | * | ||
| 224 | * @param path Filesystem path | ||
| 225 | * | ||
| 226 | * @returns True if directory creation succeeds or directory already exists, false otherwise. | ||
| 227 | */ | ||
| 228 | [[nodiscard]] bool CreateParentDirs(const std::filesystem::path& path); | ||
| 229 | |||
| 230 | #ifdef _WIN32 | ||
| 231 | template <typename Path> | ||
| 232 | [[nodiscard]] bool CreateParentDirs(const Path& path) { | ||
| 233 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 234 | return CreateParentDirs(ToU8String(path)); | ||
| 235 | } else { | ||
| 236 | return CreateParentDirs(std::filesystem::path{path}); | ||
| 237 | } | ||
| 238 | } | ||
| 239 | #endif | ||
| 240 | |||
| 241 | /** | ||
| 242 | * Removes a directory at path. | ||
| 243 | * | ||
| 244 | * Failures occur when: | ||
| 245 | * - Input path is not valid | ||
| 246 | * - Filesystem object at path is not a directory | ||
| 247 | * - The given directory is not empty | ||
| 248 | * - Filesystem at path is read only | ||
| 249 | * | ||
| 250 | * @param path Filesystem path | ||
| 251 | * | ||
| 252 | * @returns True if directory removal succeeds or directory does not exist, false otherwise. | ||
| 253 | */ | ||
| 254 | [[nodiscard]] bool RemoveDir(const std::filesystem::path& path); | ||
| 255 | |||
| 256 | #ifdef _WIN32 | ||
| 257 | template <typename Path> | ||
| 258 | [[nodiscard]] bool RemoveDir(const Path& path) { | ||
| 259 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 260 | return RemoveDir(ToU8String(path)); | ||
| 261 | } else { | ||
| 262 | return RemoveDir(std::filesystem::path{path}); | ||
| 263 | } | ||
| 264 | } | ||
| 265 | #endif | ||
| 266 | |||
| 267 | /** | ||
| 268 | * Removes all the contents within the given directory and removes the directory itself. | ||
| 269 | * | ||
| 270 | * Failures occur when: | ||
| 271 | * - Input path is not valid | ||
| 272 | * - Filesystem object at path is not a directory | ||
| 273 | * - Filesystem at path is read only | ||
| 274 | * | ||
| 275 | * @param path Filesystem path | ||
| 276 | * | ||
| 277 | * @returns True if the directory and all of its contents are removed successfully, false otherwise. | ||
| 278 | */ | ||
| 279 | [[nodiscard]] bool RemoveDirRecursively(const std::filesystem::path& path); | ||
| 280 | |||
| 281 | #ifdef _WIN32 | ||
| 282 | template <typename Path> | ||
| 283 | [[nodiscard]] bool RemoveDirRecursively(const Path& path) { | ||
| 284 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 285 | return RemoveDirRecursively(ToU8String(path)); | ||
| 286 | } else { | ||
| 287 | return RemoveDirRecursively(std::filesystem::path{path}); | ||
| 288 | } | ||
| 289 | } | ||
| 290 | #endif | ||
| 291 | |||
| 292 | /** | ||
| 293 | * Removes all the contents within the given directory without removing the directory itself. | ||
| 294 | * | ||
| 295 | * Failures occur when: | ||
| 296 | * - Input path is not valid | ||
| 297 | * - Filesystem object at path is not a directory | ||
| 298 | * - Filesystem at path is read only | ||
| 299 | * | ||
| 300 | * @param path Filesystem path | ||
| 301 | * | ||
| 302 | * @returns True if all of the directory's contents are removed successfully, false otherwise. | ||
| 303 | */ | ||
| 304 | [[nodiscard]] bool RemoveDirContentsRecursively(const std::filesystem::path& path); | ||
| 305 | |||
| 306 | #ifdef _WIN32 | ||
| 307 | template <typename Path> | ||
| 308 | [[nodiscard]] bool RemoveDirContentsRecursively(const Path& path) { | ||
| 309 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 310 | return RemoveDirContentsRecursively(ToU8String(path)); | ||
| 311 | } else { | ||
| 312 | return RemoveDirContentsRecursively(std::filesystem::path{path}); | ||
| 313 | } | ||
| 314 | } | ||
| 315 | #endif | ||
| 316 | |||
| 317 | /** | ||
| 318 | * Renames a directory from old_path to new_path. | ||
| 319 | * | ||
| 320 | * Failures occur when: | ||
| 321 | * - One or both input path(s) is not valid | ||
| 322 | * - Filesystem object at old_path does not exist | ||
| 323 | * - Filesystem object at old_path is not a directory | ||
| 324 | * - Filesystem object at new_path exists | ||
| 325 | * - Filesystem at either path is read only | ||
| 326 | * | ||
| 327 | * @param old_path Old filesystem path | ||
| 328 | * @param new_path New filesystem path | ||
| 329 | * | ||
| 330 | * @returns True if directory rename succeeds, false otherwise. | ||
| 331 | */ | ||
| 332 | [[nodiscard]] bool RenameDir(const std::filesystem::path& old_path, | ||
| 333 | const std::filesystem::path& new_path); | ||
| 334 | |||
| 335 | #ifdef _WIN32 | ||
| 336 | template <typename Path1, typename Path2> | ||
| 337 | [[nodiscard]] bool RenameDir(const Path1& old_path, const Path2& new_path) { | ||
| 338 | using ValueType1 = typename Path1::value_type; | ||
| 339 | using ValueType2 = typename Path2::value_type; | ||
| 340 | if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 341 | return RenameDir(ToU8String(old_path), ToU8String(new_path)); | ||
| 342 | } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { | ||
| 343 | return RenameDir(ToU8String(old_path), new_path); | ||
| 344 | } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 345 | return RenameDir(old_path, ToU8String(new_path)); | ||
| 346 | } else { | ||
| 347 | return RenameDir(std::filesystem::path{old_path}, std::filesystem::path{new_path}); | ||
| 348 | } | ||
| 349 | } | ||
| 350 | #endif | ||
| 351 | |||
| 352 | /** | ||
| 353 | * Iterates over the directory entries of a given directory. | ||
| 354 | * This does not iterate over the sub-directories of the given directory. | ||
| 355 | * The DirEntryCallable callback is called for each visited directory entry. | ||
| 356 | * A filter can be set to control which directory entries are visited based on their type. | ||
| 357 | * By default, both files and directories are visited. | ||
| 358 | * If the callback returns false or there is an error, the iteration is immediately halted. | ||
| 359 | * | ||
| 360 | * Failures occur when: | ||
| 361 | * - Input path is not valid | ||
| 362 | * - Filesystem object at path is not a directory | ||
| 363 | * | ||
| 364 | * @param path Filesystem path | ||
| 365 | * @param callback Callback to be called for each visited directory entry | ||
| 366 | * @param filter Directory entry type filter | ||
| 367 | */ | ||
| 368 | void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback, | ||
| 369 | DirEntryFilter filter = DirEntryFilter::All); | ||
| 370 | |||
| 371 | #ifdef _WIN32 | ||
| 372 | template <typename Path> | ||
| 373 | void IterateDirEntries(const Path& path, const DirEntryCallable& callback, | ||
| 374 | DirEntryFilter filter = DirEntryFilter::All) { | ||
| 375 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 376 | IterateDirEntries(ToU8String(path), callback, filter); | ||
| 377 | } else { | ||
| 378 | IterateDirEntries(std::filesystem::path{path}, callback, filter); | ||
| 379 | } | ||
| 380 | } | ||
| 381 | #endif | ||
| 382 | |||
| 383 | /** | ||
| 384 | * Iterates over the directory entries of a given directory and its sub-directories. | ||
| 385 | * The DirEntryCallable callback is called for each visited directory entry. | ||
| 386 | * A filter can be set to control which directory entries are visited based on their type. | ||
| 387 | * By default, both files and directories are visited. | ||
| 388 | * If the callback returns false or there is an error, the iteration is immediately halted. | ||
| 389 | * | ||
| 390 | * Failures occur when: | ||
| 391 | * - Input path is not valid | ||
| 392 | * - Filesystem object at path does not exist | ||
| 393 | * - Filesystem object at path is not a directory | ||
| 394 | * | ||
| 395 | * @param path Filesystem path | ||
| 396 | * @param callback Callback to be called for each visited directory entry | ||
| 397 | * @param filter Directory entry type filter | ||
| 398 | */ | ||
| 399 | void IterateDirEntriesRecursively(const std::filesystem::path& path, | ||
| 400 | const DirEntryCallable& callback, | ||
| 401 | DirEntryFilter filter = DirEntryFilter::All); | ||
| 402 | |||
| 403 | #ifdef _WIN32 | ||
| 404 | template <typename Path> | ||
| 405 | void IterateDirEntriesRecursively(const Path& path, const DirEntryCallable& callback, | ||
| 406 | DirEntryFilter filter = DirEntryFilter::All) { | ||
| 407 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 408 | IterateDirEntriesRecursively(ToU8String(path), callback, filter); | ||
| 409 | } else { | ||
| 410 | IterateDirEntriesRecursively(std::filesystem::path{path}, callback, filter); | ||
| 411 | } | ||
| 412 | } | ||
| 413 | #endif | ||
| 414 | |||
| 415 | // Generic Filesystem Operations | ||
| 416 | |||
| 417 | /** | ||
| 418 | * Returns whether a filesystem object at path exists. | ||
| 419 | * | ||
| 420 | * @param path Filesystem path | ||
| 421 | * | ||
| 422 | * @returns True if a filesystem object at path exists, false otherwise. | ||
| 423 | */ | ||
| 424 | [[nodiscard]] bool Exists(const std::filesystem::path& path); | ||
| 425 | |||
| 426 | #ifdef _WIN32 | ||
| 427 | template <typename Path> | ||
| 428 | [[nodiscard]] bool Exists(const Path& path) { | ||
| 429 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 430 | return Exists(ToU8String(path)); | ||
| 431 | } else { | ||
| 432 | return Exists(std::filesystem::path{path}); | ||
| 433 | } | ||
| 434 | } | ||
| 435 | #endif | ||
| 436 | |||
| 437 | /** | ||
| 438 | * Returns whether a filesystem object at path is a file. | ||
| 439 | * | ||
| 440 | * @param path Filesystem path | ||
| 441 | * | ||
| 442 | * @returns True if a filesystem object at path is a file, false otherwise. | ||
| 443 | */ | ||
| 444 | [[nodiscard]] bool IsFile(const std::filesystem::path& path); | ||
| 445 | |||
| 446 | #ifdef _WIN32 | ||
| 447 | template <typename Path> | ||
| 448 | [[nodiscard]] bool IsFile(const Path& path) { | ||
| 449 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 450 | return IsFile(ToU8String(path)); | ||
| 451 | } else { | ||
| 452 | return IsFile(std::filesystem::path{path}); | ||
| 453 | } | ||
| 454 | } | ||
| 455 | #endif | ||
| 456 | |||
| 457 | /** | ||
| 458 | * Returns whether a filesystem object at path is a directory. | ||
| 459 | * | ||
| 460 | * @param path Filesystem path | ||
| 461 | * | ||
| 462 | * @returns True if a filesystem object at path is a directory, false otherwise. | ||
| 463 | */ | ||
| 464 | [[nodiscard]] bool IsDir(const std::filesystem::path& path); | ||
| 465 | |||
| 466 | #ifdef _WIN32 | ||
| 467 | template <typename Path> | ||
| 468 | [[nodiscard]] bool IsDir(const Path& path) { | ||
| 469 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 470 | return IsDir(ToU8String(path)); | ||
| 471 | } else { | ||
| 472 | return IsDir(std::filesystem::path{path}); | ||
| 473 | } | ||
| 474 | } | ||
| 475 | #endif | ||
| 476 | |||
| 477 | /** | ||
| 478 | * Gets the current working directory. | ||
| 479 | * | ||
| 480 | * @returns The current working directory. Returns an empty path on failure. | ||
| 481 | */ | ||
| 482 | [[nodiscard]] std::filesystem::path GetCurrentDir(); | ||
| 483 | |||
| 484 | /** | ||
| 485 | * Sets the current working directory to path. | ||
| 486 | * | ||
| 487 | * @returns True if the current working directory is successfully set, false otherwise. | ||
| 488 | */ | ||
| 489 | [[nodiscard]] bool SetCurrentDir(const std::filesystem::path& path); | ||
| 490 | |||
| 491 | #ifdef _WIN32 | ||
| 492 | template <typename Path> | ||
| 493 | [[nodiscard]] bool SetCurrentDir(const Path& path) { | ||
| 494 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 495 | return SetCurrentDir(ToU8String(path)); | ||
| 496 | } else { | ||
| 497 | return SetCurrentDir(std::filesystem::path{path}); | ||
| 498 | } | ||
| 499 | } | ||
| 500 | #endif | ||
| 501 | |||
| 502 | /** | ||
| 503 | * Gets the entry type of the filesystem object at path. | ||
| 504 | * | ||
| 505 | * @param path Filesystem path | ||
| 506 | * | ||
| 507 | * @returns The entry type of the filesystem object. Returns file_type::not_found on failure. | ||
| 508 | */ | ||
| 509 | [[nodiscard]] std::filesystem::file_type GetEntryType(const std::filesystem::path& path); | ||
| 510 | |||
| 511 | #ifdef _WIN32 | ||
| 512 | template <typename Path> | ||
| 513 | [[nodiscard]] std::filesystem::file_type GetEntryType(const Path& path) { | ||
| 514 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 515 | return GetEntryType(ToU8String(path)); | ||
| 516 | } else { | ||
| 517 | return GetEntryType(std::filesystem::path{path}); | ||
| 518 | } | ||
| 519 | } | ||
| 520 | #endif | ||
| 521 | |||
| 522 | /** | ||
| 523 | * Gets the size of the filesystem object at path. | ||
| 524 | * | ||
| 525 | * @param path Filesystem path | ||
| 526 | * | ||
| 527 | * @returns The size in bytes of the filesystem object. Returns 0 on failure. | ||
| 528 | */ | ||
| 529 | [[nodiscard]] u64 GetSize(const std::filesystem::path& path); | ||
| 530 | |||
| 531 | #ifdef _WIN32 | ||
| 532 | template <typename Path> | ||
| 533 | [[nodiscard]] u64 GetSize(const Path& path) { | ||
| 534 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 535 | return GetSize(ToU8String(path)); | ||
| 536 | } else { | ||
| 537 | return GetSize(std::filesystem::path{path}); | ||
| 538 | } | ||
| 539 | } | ||
| 540 | #endif | ||
| 541 | |||
| 542 | /** | ||
| 543 | * Gets the free space size of the filesystem at path. | ||
| 544 | * | ||
| 545 | * @param path Filesystem path | ||
| 546 | * | ||
| 547 | * @returns The free space size in bytes of the filesystem at path. Returns 0 on failure. | ||
| 548 | */ | ||
| 549 | [[nodiscard]] u64 GetFreeSpaceSize(const std::filesystem::path& path); | ||
| 550 | |||
| 551 | #ifdef _WIN32 | ||
| 552 | template <typename Path> | ||
| 553 | [[nodiscard]] u64 GetFreeSpaceSize(const Path& path) { | ||
| 554 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 555 | return GetFreeSpaceSize(ToU8String(path)); | ||
| 556 | } else { | ||
| 557 | return GetFreeSpaceSize(std::filesystem::path{path}); | ||
| 558 | } | ||
| 559 | } | ||
| 560 | #endif | ||
| 561 | |||
| 562 | /** | ||
| 563 | * Gets the total capacity of the filesystem at path. | ||
| 564 | * | ||
| 565 | * @param path Filesystem path | ||
| 566 | * | ||
| 567 | * @returns The total capacity in bytes of the filesystem at path. Returns 0 on failure. | ||
| 568 | */ | ||
| 569 | [[nodiscard]] u64 GetTotalSpaceSize(const std::filesystem::path& path); | ||
| 570 | |||
| 571 | #ifdef _WIN32 | ||
| 572 | template <typename Path> | ||
| 573 | [[nodiscard]] u64 GetTotalSpaceSize(const Path& path) { | ||
| 574 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 575 | return GetTotalSpaceSize(ToU8String(path)); | ||
| 576 | } else { | ||
| 577 | return GetTotalSpaceSize(std::filesystem::path{path}); | ||
| 578 | } | ||
| 579 | } | ||
| 580 | #endif | ||
| 581 | |||
| 582 | } // namespace Common::FS | ||
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h new file mode 100644 index 000000000..b32614797 --- /dev/null +++ b/src/common/fs/fs_paths.h | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | // yuzu data directories | ||
| 8 | |||
| 9 | #define YUZU_DIR "yuzu" | ||
| 10 | #define PORTABLE_DIR "user" | ||
| 11 | |||
| 12 | // Sub-directories contained within a yuzu data directory | ||
| 13 | |||
| 14 | #define CACHE_DIR "cache" | ||
| 15 | #define CONFIG_DIR "config" | ||
| 16 | #define DUMP_DIR "dump" | ||
| 17 | #define KEYS_DIR "keys" | ||
| 18 | #define LOAD_DIR "load" | ||
| 19 | #define LOG_DIR "log" | ||
| 20 | #define NAND_DIR "nand" | ||
| 21 | #define SCREENSHOTS_DIR "screenshots" | ||
| 22 | #define SDMC_DIR "sdmc" | ||
| 23 | #define SHADER_DIR "shader" | ||
| 24 | |||
| 25 | // yuzu-specific files | ||
| 26 | |||
| 27 | #define LOG_FILE "yuzu_log.txt" | ||
diff --git a/src/common/fs/fs_types.h b/src/common/fs/fs_types.h new file mode 100644 index 000000000..089980aee --- /dev/null +++ b/src/common/fs/fs_types.h | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <functional> | ||
| 8 | |||
| 9 | #include "common/common_funcs.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace Common::FS { | ||
| 13 | |||
| 14 | enum class FileAccessMode { | ||
| 15 | /** | ||
| 16 | * If the file at path exists, it opens the file for reading. | ||
| 17 | * If the file at path does not exist, it fails to open the file. | ||
| 18 | */ | ||
| 19 | Read = 1 << 0, | ||
| 20 | /** | ||
| 21 | * If the file at path exists, the existing contents of the file are erased. | ||
| 22 | * The empty file is then opened for writing. | ||
| 23 | * If the file at path does not exist, it creates and opens a new empty file for writing. | ||
| 24 | */ | ||
| 25 | Write = 1 << 1, | ||
| 26 | /** | ||
| 27 | * If the file at path exists, it opens the file for reading and writing. | ||
| 28 | * If the file at path does not exist, it fails to open the file. | ||
| 29 | */ | ||
| 30 | ReadWrite = Read | Write, | ||
| 31 | /** | ||
| 32 | * If the file at path exists, it opens the file for appending. | ||
| 33 | * If the file at path does not exist, it creates and opens a new empty file for appending. | ||
| 34 | */ | ||
| 35 | Append = 1 << 2, | ||
| 36 | /** | ||
| 37 | * If the file at path exists, it opens the file for both reading and appending. | ||
| 38 | * If the file at path does not exist, it creates and opens a new empty file for both | ||
| 39 | * reading and appending. | ||
| 40 | */ | ||
| 41 | ReadAppend = Read | Append, | ||
| 42 | }; | ||
| 43 | |||
| 44 | enum class FileType { | ||
| 45 | BinaryFile, | ||
| 46 | TextFile, | ||
| 47 | }; | ||
| 48 | |||
| 49 | enum class FileShareFlag { | ||
| 50 | ShareNone, // Provides exclusive access to the file. | ||
| 51 | ShareReadOnly, // Provides read only shared access to the file. | ||
| 52 | ShareWriteOnly, // Provides write only shared access to the file. | ||
| 53 | ShareReadWrite, // Provides read and write shared access to the file. | ||
| 54 | }; | ||
| 55 | |||
| 56 | enum class DirEntryFilter { | ||
| 57 | File = 1 << 0, | ||
| 58 | Directory = 1 << 1, | ||
| 59 | All = File | Directory, | ||
| 60 | }; | ||
| 61 | DECLARE_ENUM_FLAG_OPERATORS(DirEntryFilter); | ||
| 62 | |||
| 63 | /** | ||
| 64 | * A callback function which takes in the path of a directory entry. | ||
| 65 | * | ||
| 66 | * @param path The path of a directory entry | ||
| 67 | * | ||
| 68 | * @returns A boolean value. | ||
| 69 | * Return true to indicate whether the callback is successful, false otherwise. | ||
| 70 | */ | ||
| 71 | using DirEntryCallable = std::function<bool(const std::filesystem::path& path)>; | ||
| 72 | |||
| 73 | } // namespace Common::FS | ||
diff --git a/src/common/fs/fs_util.cpp b/src/common/fs/fs_util.cpp new file mode 100644 index 000000000..0ddfc3131 --- /dev/null +++ b/src/common/fs/fs_util.cpp | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "common/fs/fs_util.h" | ||
| 6 | |||
| 7 | namespace Common::FS { | ||
| 8 | |||
| 9 | std::u8string ToU8String(std::string_view utf8_string) { | ||
| 10 | return std::u8string{utf8_string.begin(), utf8_string.end()}; | ||
| 11 | } | ||
| 12 | |||
| 13 | } // namespace Common::FS | ||
diff --git a/src/common/fs/fs_util.h b/src/common/fs/fs_util.h new file mode 100644 index 000000000..951df53b6 --- /dev/null +++ b/src/common/fs/fs_util.h | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <concepts> | ||
| 8 | #include <string> | ||
| 9 | #include <string_view> | ||
| 10 | |||
| 11 | namespace Common::FS { | ||
| 12 | |||
| 13 | template <typename T> | ||
| 14 | concept IsChar = std::same_as<T, char>; | ||
| 15 | |||
| 16 | /** | ||
| 17 | * Converts a UTF-8 encoded std::string or std::string_view to a std::u8string. | ||
| 18 | * | ||
| 19 | * @param utf8_string UTF-8 encoded string | ||
| 20 | * | ||
| 21 | * @returns UTF-8 encoded std::u8string. | ||
| 22 | */ | ||
| 23 | [[nodiscard]] std::u8string ToU8String(std::string_view utf8_string); | ||
| 24 | |||
| 25 | } // namespace Common::FS | ||
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp new file mode 100644 index 000000000..8b732a21c --- /dev/null +++ b/src/common/fs/path_util.cpp | |||
| @@ -0,0 +1,432 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <unordered_map> | ||
| 7 | |||
| 8 | #include "common/fs/fs.h" | ||
| 9 | #include "common/fs/fs_paths.h" | ||
| 10 | #include "common/fs/path_util.h" | ||
| 11 | #include "common/logging/log.h" | ||
| 12 | |||
| 13 | #ifdef _WIN32 | ||
| 14 | #include <shlobj.h> // Used in GetExeDirectory() | ||
| 15 | #else | ||
| 16 | #include <cstdlib> // Used in Get(Home/Data)Directory() | ||
| 17 | #include <pwd.h> // Used in GetHomeDirectory() | ||
| 18 | #include <sys/types.h> // Used in GetHomeDirectory() | ||
| 19 | #include <unistd.h> // Used in GetDataDirectory() | ||
| 20 | #endif | ||
| 21 | |||
| 22 | #ifdef __APPLE__ | ||
| 23 | #include <sys/param.h> // Used in GetBundleDirectory() | ||
| 24 | |||
| 25 | // CFURL contains __attribute__ directives that gcc does not know how to parse, so we need to just | ||
| 26 | // ignore them if we're not using clang. The macro is only used to prevent linking against | ||
| 27 | // functions that don't exist on older versions of macOS, and the worst case scenario is a linker | ||
| 28 | // error, so this is perfectly safe, just inconvenient. | ||
| 29 | #ifndef __clang__ | ||
| 30 | #define availability(...) | ||
| 31 | #endif | ||
| 32 | #include <CoreFoundation/CFBundle.h> // Used in GetBundleDirectory() | ||
| 33 | #include <CoreFoundation/CFString.h> // Used in GetBundleDirectory() | ||
| 34 | #include <CoreFoundation/CFURL.h> // Used in GetBundleDirectory() | ||
| 35 | #ifdef availability | ||
| 36 | #undef availability | ||
| 37 | #endif | ||
| 38 | #endif | ||
| 39 | |||
| 40 | #ifndef MAX_PATH | ||
| 41 | #ifdef _WIN32 | ||
| 42 | // This is the maximum number of UTF-16 code units permissible in Windows file paths | ||
| 43 | #define MAX_PATH 260 | ||
| 44 | #else | ||
| 45 | // This is the maximum number of UTF-8 code units permissible in all other OSes' file paths | ||
| 46 | #define MAX_PATH 1024 | ||
| 47 | #endif | ||
| 48 | #endif | ||
| 49 | |||
| 50 | namespace Common::FS { | ||
| 51 | |||
| 52 | namespace fs = std::filesystem; | ||
| 53 | |||
| 54 | /** | ||
| 55 | * The PathManagerImpl is a singleton allowing to manage the mapping of | ||
| 56 | * YuzuPath enums to real filesystem paths. | ||
| 57 | * This class provides 2 functions: GetYuzuPathImpl and SetYuzuPathImpl. | ||
| 58 | * These are used by GetYuzuPath and SetYuzuPath respectively to get or modify | ||
| 59 | * the path mapped by the YuzuPath enum. | ||
| 60 | */ | ||
| 61 | class PathManagerImpl { | ||
| 62 | public: | ||
| 63 | static PathManagerImpl& GetInstance() { | ||
| 64 | static PathManagerImpl path_manager_impl; | ||
| 65 | |||
| 66 | return path_manager_impl; | ||
| 67 | } | ||
| 68 | |||
| 69 | PathManagerImpl(const PathManagerImpl&) = delete; | ||
| 70 | PathManagerImpl& operator=(const PathManagerImpl&) = delete; | ||
| 71 | |||
| 72 | PathManagerImpl(PathManagerImpl&&) = delete; | ||
| 73 | PathManagerImpl& operator=(PathManagerImpl&&) = delete; | ||
| 74 | |||
| 75 | [[nodiscard]] const fs::path& GetYuzuPathImpl(YuzuPath yuzu_path) { | ||
| 76 | return yuzu_paths.at(yuzu_path); | ||
| 77 | } | ||
| 78 | |||
| 79 | void SetYuzuPathImpl(YuzuPath yuzu_path, const fs::path& new_path) { | ||
| 80 | yuzu_paths.insert_or_assign(yuzu_path, new_path); | ||
| 81 | } | ||
| 82 | |||
| 83 | private: | ||
| 84 | PathManagerImpl() { | ||
| 85 | #ifdef _WIN32 | ||
| 86 | auto yuzu_path = GetExeDirectory() / PORTABLE_DIR; | ||
| 87 | |||
| 88 | if (!IsDir(yuzu_path)) { | ||
| 89 | yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR; | ||
| 90 | } | ||
| 91 | |||
| 92 | GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path); | ||
| 93 | GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path / CACHE_DIR); | ||
| 94 | GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path / CONFIG_DIR); | ||
| 95 | #else | ||
| 96 | auto yuzu_path = GetCurrentDir() / PORTABLE_DIR; | ||
| 97 | |||
| 98 | if (Exists(yuzu_path) && IsDir(yuzu_path)) { | ||
| 99 | GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path); | ||
| 100 | GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path / CACHE_DIR); | ||
| 101 | GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path / CONFIG_DIR); | ||
| 102 | } else { | ||
| 103 | yuzu_path = GetDataDirectory("XDG_DATA_HOME") / YUZU_DIR; | ||
| 104 | |||
| 105 | GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path); | ||
| 106 | GenerateYuzuPath(YuzuPath::CacheDir, GetDataDirectory("XDG_CACHE_HOME") / YUZU_DIR); | ||
| 107 | GenerateYuzuPath(YuzuPath::ConfigDir, GetDataDirectory("XDG_CONFIG_HOME") / YUZU_DIR); | ||
| 108 | } | ||
| 109 | #endif | ||
| 110 | |||
| 111 | GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR); | ||
| 112 | GenerateYuzuPath(YuzuPath::KeysDir, yuzu_path / KEYS_DIR); | ||
| 113 | GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); | ||
| 114 | GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR); | ||
| 115 | GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR); | ||
| 116 | GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); | ||
| 117 | GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); | ||
| 118 | GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); | ||
| 119 | } | ||
| 120 | |||
| 121 | ~PathManagerImpl() = default; | ||
| 122 | |||
| 123 | void GenerateYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) { | ||
| 124 | void(FS::CreateDir(new_path)); | ||
| 125 | |||
| 126 | SetYuzuPathImpl(yuzu_path, new_path); | ||
| 127 | } | ||
| 128 | |||
| 129 | std::unordered_map<YuzuPath, fs::path> yuzu_paths; | ||
| 130 | }; | ||
| 131 | |||
| 132 | std::string PathToUTF8String(const fs::path& path) { | ||
| 133 | const auto utf8_string = path.u8string(); | ||
| 134 | |||
| 135 | return std::string{utf8_string.begin(), utf8_string.end()}; | ||
| 136 | } | ||
| 137 | |||
| 138 | bool ValidatePath(const fs::path& path) { | ||
| 139 | if (path.empty()) { | ||
| 140 | LOG_ERROR(Common_Filesystem, "Input path is empty, path={}", PathToUTF8String(path)); | ||
| 141 | return false; | ||
| 142 | } | ||
| 143 | |||
| 144 | #ifdef _WIN32 | ||
| 145 | if (path.u16string().size() >= MAX_PATH) { | ||
| 146 | LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path)); | ||
| 147 | return false; | ||
| 148 | } | ||
| 149 | #else | ||
| 150 | if (path.u8string().size() >= MAX_PATH) { | ||
| 151 | LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path)); | ||
| 152 | return false; | ||
| 153 | } | ||
| 154 | #endif | ||
| 155 | |||
| 156 | return true; | ||
| 157 | } | ||
| 158 | |||
| 159 | fs::path ConcatPath(const fs::path& first, const fs::path& second) { | ||
| 160 | const bool second_has_dir_sep = IsDirSeparator(second.u8string().front()); | ||
| 161 | |||
| 162 | if (!second_has_dir_sep) { | ||
| 163 | return (first / second).lexically_normal(); | ||
| 164 | } | ||
| 165 | |||
| 166 | fs::path concat_path = first; | ||
| 167 | concat_path += second; | ||
| 168 | |||
| 169 | return concat_path.lexically_normal(); | ||
| 170 | } | ||
| 171 | |||
| 172 | fs::path ConcatPathSafe(const fs::path& base, const fs::path& offset) { | ||
| 173 | const auto concatenated_path = ConcatPath(base, offset); | ||
| 174 | |||
| 175 | if (!IsPathSandboxed(base, concatenated_path)) { | ||
| 176 | return base; | ||
| 177 | } | ||
| 178 | |||
| 179 | return concatenated_path; | ||
| 180 | } | ||
| 181 | |||
| 182 | bool IsPathSandboxed(const fs::path& base, const fs::path& path) { | ||
| 183 | const auto base_string = RemoveTrailingSeparators(base.lexically_normal()).u8string(); | ||
| 184 | const auto path_string = RemoveTrailingSeparators(path.lexically_normal()).u8string(); | ||
| 185 | |||
| 186 | if (path_string.size() < base_string.size()) { | ||
| 187 | return false; | ||
| 188 | } | ||
| 189 | |||
| 190 | return base_string.compare(0, base_string.size(), path_string, 0, base_string.size()) == 0; | ||
| 191 | } | ||
| 192 | |||
| 193 | bool IsDirSeparator(char character) { | ||
| 194 | return character == '/' || character == '\\'; | ||
| 195 | } | ||
| 196 | |||
| 197 | bool IsDirSeparator(char8_t character) { | ||
| 198 | return character == u8'/' || character == u8'\\'; | ||
| 199 | } | ||
| 200 | |||
| 201 | fs::path RemoveTrailingSeparators(const fs::path& path) { | ||
| 202 | if (path.empty()) { | ||
| 203 | return path; | ||
| 204 | } | ||
| 205 | |||
| 206 | auto string_path = path.u8string(); | ||
| 207 | |||
| 208 | while (IsDirSeparator(string_path.back())) { | ||
| 209 | string_path.pop_back(); | ||
| 210 | } | ||
| 211 | |||
| 212 | return fs::path{string_path}; | ||
| 213 | } | ||
| 214 | |||
| 215 | const fs::path& GetYuzuPath(YuzuPath yuzu_path) { | ||
| 216 | return PathManagerImpl::GetInstance().GetYuzuPathImpl(yuzu_path); | ||
| 217 | } | ||
| 218 | |||
| 219 | std::string GetYuzuPathString(YuzuPath yuzu_path) { | ||
| 220 | return PathToUTF8String(GetYuzuPath(yuzu_path)); | ||
| 221 | } | ||
| 222 | |||
| 223 | void SetYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) { | ||
| 224 | if (!FS::IsDir(new_path)) { | ||
| 225 | LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} is not a directory", | ||
| 226 | PathToUTF8String(new_path)); | ||
| 227 | return; | ||
| 228 | } | ||
| 229 | |||
| 230 | PathManagerImpl::GetInstance().SetYuzuPathImpl(yuzu_path, new_path); | ||
| 231 | } | ||
| 232 | |||
| 233 | #ifdef _WIN32 | ||
| 234 | |||
| 235 | fs::path GetExeDirectory() { | ||
| 236 | wchar_t exe_path[MAX_PATH]; | ||
| 237 | |||
| 238 | GetModuleFileNameW(nullptr, exe_path, MAX_PATH); | ||
| 239 | |||
| 240 | if (!exe_path) { | ||
| 241 | LOG_ERROR(Common_Filesystem, | ||
| 242 | "Failed to get the path to the executable of the current process"); | ||
| 243 | } | ||
| 244 | |||
| 245 | return fs::path{exe_path}.parent_path(); | ||
| 246 | } | ||
| 247 | |||
| 248 | fs::path GetAppDataRoamingDirectory() { | ||
| 249 | PWSTR appdata_roaming_path = nullptr; | ||
| 250 | |||
| 251 | SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &appdata_roaming_path); | ||
| 252 | |||
| 253 | auto fs_appdata_roaming_path = fs::path{appdata_roaming_path}; | ||
| 254 | |||
| 255 | CoTaskMemFree(appdata_roaming_path); | ||
| 256 | |||
| 257 | if (fs_appdata_roaming_path.empty()) { | ||
| 258 | LOG_ERROR(Common_Filesystem, "Failed to get the path to the %APPDATA% directory"); | ||
| 259 | } | ||
| 260 | |||
| 261 | return fs_appdata_roaming_path; | ||
| 262 | } | ||
| 263 | |||
| 264 | #else | ||
| 265 | |||
| 266 | fs::path GetHomeDirectory() { | ||
| 267 | const char* home_env_var = getenv("HOME"); | ||
| 268 | |||
| 269 | if (home_env_var) { | ||
| 270 | return fs::path{home_env_var}; | ||
| 271 | } | ||
| 272 | |||
| 273 | LOG_INFO(Common_Filesystem, | ||
| 274 | "$HOME is not defined in the environment variables, " | ||
| 275 | "attempting to query passwd to get the home path of the current user"); | ||
| 276 | |||
| 277 | const auto* pw = getpwuid(getuid()); | ||
| 278 | |||
| 279 | if (!pw) { | ||
| 280 | LOG_ERROR(Common_Filesystem, "Failed to get the home path of the current user"); | ||
| 281 | return {}; | ||
| 282 | } | ||
| 283 | |||
| 284 | return fs::path{pw->pw_dir}; | ||
| 285 | } | ||
| 286 | |||
| 287 | fs::path GetDataDirectory(const std::string& env_name) { | ||
| 288 | const char* data_env_var = getenv(env_name.c_str()); | ||
| 289 | |||
| 290 | if (data_env_var) { | ||
| 291 | return fs::path{data_env_var}; | ||
| 292 | } | ||
| 293 | |||
| 294 | if (env_name == "XDG_DATA_HOME") { | ||
| 295 | return GetHomeDirectory() / ".local/share"; | ||
| 296 | } else if (env_name == "XDG_CACHE_HOME") { | ||
| 297 | return GetHomeDirectory() / ".cache"; | ||
| 298 | } else if (env_name == "XDG_CONFIG_HOME") { | ||
| 299 | return GetHomeDirectory() / ".config"; | ||
| 300 | } | ||
| 301 | |||
| 302 | return {}; | ||
| 303 | } | ||
| 304 | |||
| 305 | #endif | ||
| 306 | |||
| 307 | #ifdef __APPLE__ | ||
| 308 | |||
| 309 | fs::path GetBundleDirectory() { | ||
| 310 | char app_bundle_path[MAXPATHLEN]; | ||
| 311 | |||
| 312 | // Get the main bundle for the app | ||
| 313 | CFURLRef bundle_ref = CFBundleCopyBundleURL(CFBundleGetMainBundle()); | ||
| 314 | CFStringRef bundle_path = CFURLCopyFileSystemPath(bundle_ref, kCFURLPOSIXPathStyle); | ||
| 315 | |||
| 316 | CFStringGetFileSystemRepresentation(bundle_path, app_bundle_path, sizeof(app_bundle_path)); | ||
| 317 | |||
| 318 | CFRelease(bundle_ref); | ||
| 319 | CFRelease(bundle_path); | ||
| 320 | |||
| 321 | return fs::path{app_bundle_path}; | ||
| 322 | } | ||
| 323 | |||
| 324 | #endif | ||
| 325 | |||
| 326 | // vvvvvvvvvv Deprecated vvvvvvvvvv // | ||
| 327 | |||
| 328 | std::string_view RemoveTrailingSlash(std::string_view path) { | ||
| 329 | if (path.empty()) { | ||
| 330 | return path; | ||
| 331 | } | ||
| 332 | |||
| 333 | if (path.back() == '\\' || path.back() == '/') { | ||
| 334 | path.remove_suffix(1); | ||
| 335 | return path; | ||
| 336 | } | ||
| 337 | |||
| 338 | return path; | ||
| 339 | } | ||
| 340 | |||
| 341 | std::vector<std::string> SplitPathComponents(std::string_view filename) { | ||
| 342 | std::string copy(filename); | ||
| 343 | std::replace(copy.begin(), copy.end(), '\\', '/'); | ||
| 344 | std::vector<std::string> out; | ||
| 345 | |||
| 346 | std::stringstream stream(copy); | ||
| 347 | std::string item; | ||
| 348 | while (std::getline(stream, item, '/')) { | ||
| 349 | out.push_back(std::move(item)); | ||
| 350 | } | ||
| 351 | |||
| 352 | return out; | ||
| 353 | } | ||
| 354 | |||
| 355 | std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) { | ||
| 356 | std::string path(path_); | ||
| 357 | char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\'; | ||
| 358 | char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/'; | ||
| 359 | |||
| 360 | if (directory_separator == DirectorySeparator::PlatformDefault) { | ||
| 361 | #ifdef _WIN32 | ||
| 362 | type1 = '/'; | ||
| 363 | type2 = '\\'; | ||
| 364 | #endif | ||
| 365 | } | ||
| 366 | |||
| 367 | std::replace(path.begin(), path.end(), type1, type2); | ||
| 368 | |||
| 369 | auto start = path.begin(); | ||
| 370 | #ifdef _WIN32 | ||
| 371 | // allow network paths which start with a double backslash (e.g. \\server\share) | ||
| 372 | if (start != path.end()) | ||
| 373 | ++start; | ||
| 374 | #endif | ||
| 375 | path.erase(std::unique(start, path.end(), | ||
| 376 | [type2](char c1, char c2) { return c1 == type2 && c2 == type2; }), | ||
| 377 | path.end()); | ||
| 378 | return std::string(RemoveTrailingSlash(path)); | ||
| 379 | } | ||
| 380 | |||
| 381 | std::string_view GetParentPath(std::string_view path) { | ||
| 382 | const auto name_bck_index = path.rfind('\\'); | ||
| 383 | const auto name_fwd_index = path.rfind('/'); | ||
| 384 | std::size_t name_index; | ||
| 385 | |||
| 386 | if (name_bck_index == std::string_view::npos || name_fwd_index == std::string_view::npos) { | ||
| 387 | name_index = std::min(name_bck_index, name_fwd_index); | ||
| 388 | } else { | ||
| 389 | name_index = std::max(name_bck_index, name_fwd_index); | ||
| 390 | } | ||
| 391 | |||
| 392 | return path.substr(0, name_index); | ||
| 393 | } | ||
| 394 | |||
| 395 | std::string_view GetPathWithoutTop(std::string_view path) { | ||
| 396 | if (path.empty()) { | ||
| 397 | return path; | ||
| 398 | } | ||
| 399 | |||
| 400 | while (path[0] == '\\' || path[0] == '/') { | ||
| 401 | path.remove_prefix(1); | ||
| 402 | if (path.empty()) { | ||
| 403 | return path; | ||
| 404 | } | ||
| 405 | } | ||
| 406 | |||
| 407 | const auto name_bck_index = path.find('\\'); | ||
| 408 | const auto name_fwd_index = path.find('/'); | ||
| 409 | return path.substr(std::min(name_bck_index, name_fwd_index) + 1); | ||
| 410 | } | ||
| 411 | |||
| 412 | std::string_view GetFilename(std::string_view path) { | ||
| 413 | const auto name_index = path.find_last_of("\\/"); | ||
| 414 | |||
| 415 | if (name_index == std::string_view::npos) { | ||
| 416 | return {}; | ||
| 417 | } | ||
| 418 | |||
| 419 | return path.substr(name_index + 1); | ||
| 420 | } | ||
| 421 | |||
| 422 | std::string_view GetExtensionFromFilename(std::string_view name) { | ||
| 423 | const std::size_t index = name.rfind('.'); | ||
| 424 | |||
| 425 | if (index == std::string_view::npos) { | ||
| 426 | return {}; | ||
| 427 | } | ||
| 428 | |||
| 429 | return name.substr(index + 1); | ||
| 430 | } | ||
| 431 | |||
| 432 | } // namespace Common::FS | ||
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h new file mode 100644 index 000000000..a9fadbceb --- /dev/null +++ b/src/common/fs/path_util.h | |||
| @@ -0,0 +1,309 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <filesystem> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "common/fs/fs_util.h" | ||
| 11 | |||
| 12 | namespace Common::FS { | ||
| 13 | |||
| 14 | enum class YuzuPath { | ||
| 15 | YuzuDir, // Where yuzu stores its data. | ||
| 16 | CacheDir, // Where cached filesystem data is stored. | ||
| 17 | ConfigDir, // Where config files are stored. | ||
| 18 | DumpDir, // Where dumped data is stored. | ||
| 19 | KeysDir, // Where key files are stored. | ||
| 20 | LoadDir, // Where cheat/mod files are stored. | ||
| 21 | LogDir, // Where log files are stored. | ||
| 22 | NANDDir, // Where the emulated NAND is stored. | ||
| 23 | ScreenshotsDir, // Where yuzu screenshots are stored. | ||
| 24 | SDMCDir, // Where the emulated SDMC is stored. | ||
| 25 | ShaderDir, // Where shaders are stored. | ||
| 26 | }; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Converts a filesystem path to a UTF-8 encoded std::string. | ||
| 30 | * | ||
| 31 | * @param path Filesystem path | ||
| 32 | * | ||
| 33 | * @returns UTF-8 encoded std::string. | ||
| 34 | */ | ||
| 35 | [[nodiscard]] std::string PathToUTF8String(const std::filesystem::path& path); | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Validates a given path. | ||
| 39 | * | ||
| 40 | * A given path is valid if it meets these conditions: | ||
| 41 | * - The path is not empty | ||
| 42 | * - The path is not too long | ||
| 43 | * | ||
| 44 | * @param path Filesystem path | ||
| 45 | * | ||
| 46 | * @returns True if the path is valid, false otherwise. | ||
| 47 | */ | ||
| 48 | [[nodiscard]] bool ValidatePath(const std::filesystem::path& path); | ||
| 49 | |||
| 50 | #ifdef _WIN32 | ||
| 51 | template <typename Path> | ||
| 52 | [[nodiscard]] bool ValidatePath(const Path& path) { | ||
| 53 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 54 | return ValidatePath(ToU8String(path)); | ||
| 55 | } else { | ||
| 56 | return ValidatePath(std::filesystem::path{path}); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | #endif | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Concatenates two filesystem paths together. | ||
| 63 | * | ||
| 64 | * This is needed since the following occurs when using std::filesystem::path's operator/: | ||
| 65 | * first: "/first/path" | ||
| 66 | * second: "/second/path" (Note that the second path has a directory separator in the front) | ||
| 67 | * first / second yields "/second/path" when the desired result is first/path/second/path | ||
| 68 | * | ||
| 69 | * @param first First filesystem path | ||
| 70 | * @param second Second filesystem path | ||
| 71 | * | ||
| 72 | * @returns A concatenated filesystem path. | ||
| 73 | */ | ||
| 74 | [[nodiscard]] std::filesystem::path ConcatPath(const std::filesystem::path& first, | ||
| 75 | const std::filesystem::path& second); | ||
| 76 | |||
| 77 | #ifdef _WIN32 | ||
| 78 | template <typename Path1, typename Path2> | ||
| 79 | [[nodiscard]] std::filesystem::path ConcatPath(const Path1& first, const Path2& second) { | ||
| 80 | using ValueType1 = typename Path1::value_type; | ||
| 81 | using ValueType2 = typename Path2::value_type; | ||
| 82 | if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 83 | return ConcatPath(ToU8String(first), ToU8String(second)); | ||
| 84 | } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { | ||
| 85 | return ConcatPath(ToU8String(first), second); | ||
| 86 | } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 87 | return ConcatPath(first, ToU8String(second)); | ||
| 88 | } else { | ||
| 89 | return ConcatPath(std::filesystem::path{first}, std::filesystem::path{second}); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | #endif | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Safe variant of ConcatPath that takes in a base path and an offset path from the given base path. | ||
| 96 | * | ||
| 97 | * If ConcatPath(base, offset) resolves to a path that is sandboxed within the base path, | ||
| 98 | * this will return the concatenated path. Otherwise this will return the base path. | ||
| 99 | * | ||
| 100 | * @param base Base filesystem path | ||
| 101 | * @param offset Offset filesystem path | ||
| 102 | * | ||
| 103 | * @returns A concatenated filesystem path if it is within the base path, | ||
| 104 | * returns the base path otherwise. | ||
| 105 | */ | ||
| 106 | [[nodiscard]] std::filesystem::path ConcatPathSafe(const std::filesystem::path& base, | ||
| 107 | const std::filesystem::path& offset); | ||
| 108 | |||
| 109 | #ifdef _WIN32 | ||
| 110 | template <typename Path1, typename Path2> | ||
| 111 | [[nodiscard]] std::filesystem::path ConcatPathSafe(const Path1& base, const Path2& offset) { | ||
| 112 | using ValueType1 = typename Path1::value_type; | ||
| 113 | using ValueType2 = typename Path2::value_type; | ||
| 114 | if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 115 | return ConcatPathSafe(ToU8String(base), ToU8String(offset)); | ||
| 116 | } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { | ||
| 117 | return ConcatPathSafe(ToU8String(base), offset); | ||
| 118 | } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 119 | return ConcatPathSafe(base, ToU8String(offset)); | ||
| 120 | } else { | ||
| 121 | return ConcatPathSafe(std::filesystem::path{base}, std::filesystem::path{offset}); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | #endif | ||
| 125 | |||
| 126 | /** | ||
| 127 | * Checks whether a given path is sandboxed within a given base path. | ||
| 128 | * | ||
| 129 | * @param base Base filesystem path | ||
| 130 | * @param path Filesystem path | ||
| 131 | * | ||
| 132 | * @returns True if the given path is sandboxed within the given base path, false otherwise. | ||
| 133 | */ | ||
| 134 | [[nodiscard]] bool IsPathSandboxed(const std::filesystem::path& base, | ||
| 135 | const std::filesystem::path& path); | ||
| 136 | |||
| 137 | #ifdef _WIN32 | ||
| 138 | template <typename Path1, typename Path2> | ||
| 139 | [[nodiscard]] bool IsPathSandboxed(const Path1& base, const Path2& path) { | ||
| 140 | using ValueType1 = typename Path1::value_type; | ||
| 141 | using ValueType2 = typename Path2::value_type; | ||
| 142 | if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 143 | return IsPathSandboxed(ToU8String(base), ToU8String(path)); | ||
| 144 | } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { | ||
| 145 | return IsPathSandboxed(ToU8String(base), path); | ||
| 146 | } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 147 | return IsPathSandboxed(base, ToU8String(path)); | ||
| 148 | } else { | ||
| 149 | return IsPathSandboxed(std::filesystem::path{base}, std::filesystem::path{path}); | ||
| 150 | } | ||
| 151 | } | ||
| 152 | #endif | ||
| 153 | |||
| 154 | /** | ||
| 155 | * Checks if a character is a directory separator (either a forward slash or backslash). | ||
| 156 | * | ||
| 157 | * @param character Character | ||
| 158 | * | ||
| 159 | * @returns True if the character is a directory separator, false otherwise. | ||
| 160 | */ | ||
| 161 | [[nodiscard]] bool IsDirSeparator(char character); | ||
| 162 | |||
| 163 | /** | ||
| 164 | * Checks if a character is a directory separator (either a forward slash or backslash). | ||
| 165 | * | ||
| 166 | * @param character Character | ||
| 167 | * | ||
| 168 | * @returns True if the character is a directory separator, false otherwise. | ||
| 169 | */ | ||
| 170 | [[nodiscard]] bool IsDirSeparator(char8_t character); | ||
| 171 | |||
| 172 | /** | ||
| 173 | * Removes any trailing directory separators if they exist in the given path. | ||
| 174 | * | ||
| 175 | * @param path Filesystem path | ||
| 176 | * | ||
| 177 | * @returns The filesystem path without any trailing directory separators. | ||
| 178 | */ | ||
| 179 | [[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const std::filesystem::path& path); | ||
| 180 | |||
| 181 | #ifdef _WIN32 | ||
| 182 | template <typename Path> | ||
| 183 | [[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const Path& path) { | ||
| 184 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 185 | return RemoveTrailingSeparators(ToU8String(path)); | ||
| 186 | } else { | ||
| 187 | return RemoveTrailingSeparators(std::filesystem::path{path}); | ||
| 188 | } | ||
| 189 | } | ||
| 190 | #endif | ||
| 191 | |||
| 192 | /** | ||
| 193 | * Gets the filesystem path associated with the YuzuPath enum. | ||
| 194 | * | ||
| 195 | * @param yuzu_path YuzuPath enum | ||
| 196 | * | ||
| 197 | * @returns The filesystem path associated with the YuzuPath enum. | ||
| 198 | */ | ||
| 199 | [[nodiscard]] const std::filesystem::path& GetYuzuPath(YuzuPath yuzu_path); | ||
| 200 | |||
| 201 | /** | ||
| 202 | * Gets the filesystem path associated with the YuzuPath enum as a UTF-8 encoded std::string. | ||
| 203 | * | ||
| 204 | * @param yuzu_path YuzuPath enum | ||
| 205 | * | ||
| 206 | * @returns The filesystem path associated with the YuzuPath enum as a UTF-8 encoded std::string. | ||
| 207 | */ | ||
| 208 | [[nodiscard]] std::string GetYuzuPathString(YuzuPath yuzu_path); | ||
| 209 | |||
| 210 | /** | ||
| 211 | * Sets a new filesystem path associated with the YuzuPath enum. | ||
| 212 | * If the filesystem object at new_path is not a directory, this function will not do anything. | ||
| 213 | * | ||
| 214 | * @param yuzu_path YuzuPath enum | ||
| 215 | * @param new_path New filesystem path | ||
| 216 | */ | ||
| 217 | void SetYuzuPath(YuzuPath yuzu_path, const std::filesystem::path& new_path); | ||
| 218 | |||
| 219 | #ifdef _WIN32 | ||
| 220 | template <typename Path> | ||
| 221 | [[nodiscard]] void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) { | ||
| 222 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 223 | SetYuzuPath(yuzu_path, ToU8String(new_path)); | ||
| 224 | } else { | ||
| 225 | SetYuzuPath(yuzu_path, std::filesystem::path{new_path}); | ||
| 226 | } | ||
| 227 | } | ||
| 228 | #endif | ||
| 229 | |||
| 230 | #ifdef _WIN32 | ||
| 231 | |||
| 232 | /** | ||
| 233 | * Gets the path of the directory containing the executable of the current process. | ||
| 234 | * | ||
| 235 | * @returns The path of the directory containing the executable of the current process. | ||
| 236 | */ | ||
| 237 | [[nodiscard]] std::filesystem::path GetExeDirectory(); | ||
| 238 | |||
| 239 | /** | ||
| 240 | * Gets the path of the current user's %APPDATA% directory (%USERPROFILE%/AppData/Roaming). | ||
| 241 | * | ||
| 242 | * @returns The path of the current user's %APPDATA% directory. | ||
| 243 | */ | ||
| 244 | [[nodiscard]] std::filesystem::path GetAppDataRoamingDirectory(); | ||
| 245 | |||
| 246 | #else | ||
| 247 | |||
| 248 | /** | ||
| 249 | * Gets the path of the directory specified by the #HOME environment variable. | ||
| 250 | * If $HOME is not defined, it will attempt to query the user database in passwd instead. | ||
| 251 | * | ||
| 252 | * @returns The path of the current user's home directory. | ||
| 253 | */ | ||
| 254 | [[nodiscard]] std::filesystem::path GetHomeDirectory(); | ||
| 255 | |||
| 256 | /** | ||
| 257 | * Gets the relevant paths for yuzu to store its data based on the given XDG environment variable. | ||
| 258 | * See https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html | ||
| 259 | * Defaults to $HOME/.local/share for main application data, | ||
| 260 | * $HOME/.cache for cached data, and $HOME/.config for configuration files. | ||
| 261 | * | ||
| 262 | * @param env_name XDG environment variable name | ||
| 263 | * | ||
| 264 | * @returns The path where yuzu should store its data. | ||
| 265 | */ | ||
| 266 | [[nodiscard]] std::filesystem::path GetDataDirectory(const std::string& env_name); | ||
| 267 | |||
| 268 | #endif | ||
| 269 | |||
| 270 | #ifdef __APPLE__ | ||
| 271 | |||
| 272 | [[nodiscard]] std::filesystem::path GetBundleDirectory(); | ||
| 273 | |||
| 274 | #endif | ||
| 275 | |||
| 276 | // vvvvvvvvvv Deprecated vvvvvvvvvv // | ||
| 277 | |||
| 278 | // Removes the final '/' or '\' if one exists | ||
| 279 | [[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path); | ||
| 280 | |||
| 281 | enum class DirectorySeparator { | ||
| 282 | ForwardSlash, | ||
| 283 | BackwardSlash, | ||
| 284 | PlatformDefault, | ||
| 285 | }; | ||
| 286 | |||
| 287 | // Splits the path on '/' or '\' and put the components into a vector | ||
| 288 | // i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" } | ||
| 289 | [[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename); | ||
| 290 | |||
| 291 | // Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\' | ||
| 292 | // depending if directory_separator is BackwardSlash or PlatformDefault and running on windows | ||
| 293 | [[nodiscard]] std::string SanitizePath( | ||
| 294 | std::string_view path, | ||
| 295 | DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash); | ||
| 296 | |||
| 297 | // Gets all of the text up to the last '/' or '\' in the path. | ||
| 298 | [[nodiscard]] std::string_view GetParentPath(std::string_view path); | ||
| 299 | |||
| 300 | // Gets all of the text after the first '/' or '\' in the path. | ||
| 301 | [[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path); | ||
| 302 | |||
| 303 | // Gets the filename of the path | ||
| 304 | [[nodiscard]] std::string_view GetFilename(std::string_view path); | ||
| 305 | |||
| 306 | // Gets the extension of the filename | ||
| 307 | [[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name); | ||
| 308 | |||
| 309 | } // namespace Common::FS | ||