diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 249fbbb6f7..d59e34daec 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "common/assert.h" #include "common/common_funcs.h" @@ -355,12 +356,12 @@ u64 GetSize(FILE* f) { // can't use off_t here because it can be 32-bit u64 pos = ftello(f); if (fseeko(f, 0, SEEK_END) != 0) { - LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", (void*)f, GetLastErrorMsg()); + LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f), GetLastErrorMsg()); return 0; } u64 size = ftello(f); if ((size != pos) && (fseeko(f, pos, SEEK_SET) != 0)) { - LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", (void*)f, GetLastErrorMsg()); + LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f), GetLastErrorMsg()); return 0; } return size; @@ -369,7 +370,7 @@ u64 GetSize(FILE* f) { bool CreateEmptyFile(const std::string& filename) { LOG_TRACE(Common_Filesystem, "{}", filename); - if (!FileUtil::IOFile(filename, "wb")) { + if (!FileUtil::IOFile(filename, "wb").IsOpen()) { LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg()); return false; } @@ -541,12 +542,11 @@ std::optional GetCurrentDir() { // Get the current working directory (getcwd uses malloc) #ifdef _WIN32 wchar_t* dir; - if (!(dir = _wgetcwd(nullptr, 0))) + if (!(dir = _wgetcwd(nullptr, 0))) { #else char* dir; - if (!(dir = getcwd(nullptr, 0))) + if (!(dir = getcwd(nullptr, 0))) { #endif - { LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg()); return {}; } @@ -557,7 +557,7 @@ std::optional GetCurrentDir() { #endif free(dir); return strDir; -} +} // namespace FileUtil bool SetCurrentDir(const std::string& directory) { #ifdef _WIN32 @@ -733,7 +733,6 @@ const std::string& GetUserPath(UserPath path) { SetUserPath(); return g_paths[path]; } - std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str) { return IOFile(filename, text_file ? "w" : "wb").WriteString(str); } @@ -741,8 +740,8 @@ std::size_t WriteStringToFile(bool text_file, const std::string& filename, std:: std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str) { IOFile file(filename, text_file ? "r" : "rb"); - if (!file) - return false; + if (!file.IsOpen()) + return 0; str.resize(static_cast(file.GetSize())); return file.ReadArray(&str[0], str.size()); @@ -783,6 +782,103 @@ void SplitFilename83(const std::string& filename, std::array& short_nam } } +std::vector SplitPathComponents(std::string_view filename) { + std::string copy(filename); + std::replace(copy.begin(), copy.end(), '\\', '/'); + std::vector out; + + std::stringstream stream(copy); + std::string item; + while (std::getline(stream, item, '/')) { + out.push_back(std::move(item)); + } + + return out; +} + +std::string_view GetParentPath(std::string_view path) { + const auto name_bck_index = path.rfind('\\'); + const auto name_fwd_index = path.rfind('/'); + std::size_t name_index; + + if (name_bck_index == std::string_view::npos || name_fwd_index == std::string_view::npos) { + name_index = std::min(name_bck_index, name_fwd_index); + } else { + name_index = std::max(name_bck_index, name_fwd_index); + } + + return path.substr(0, name_index); +} + +std::string_view GetPathWithoutTop(std::string_view path) { + if (path.empty()) { + return path; + } + + while (path[0] == '\\' || path[0] == '/') { + path.remove_prefix(1); + if (path.empty()) { + return path; + } + } + + const auto name_bck_index = path.find('\\'); + const auto name_fwd_index = path.find('/'); + return path.substr(std::min(name_bck_index, name_fwd_index) + 1); +} + +std::string_view GetFilename(std::string_view path) { + const auto name_index = path.find_last_of("\\/"); + + if (name_index == std::string_view::npos) { + return {}; + } + + return path.substr(name_index + 1); +} + +std::string_view GetExtensionFromFilename(std::string_view name) { + const std::size_t index = name.rfind('.'); + + if (index == std::string_view::npos) { + return {}; + } + + return name.substr(index + 1); +} + +std::string_view RemoveTrailingSlash(std::string_view path) { + if (path.empty()) { + return path; + } + + if (path.back() == '\\' || path.back() == '/') { + path.remove_suffix(1); + return path; + } + + return path; +} + +std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) { + std::string path(path_); + char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\'; + char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/'; + + if (directory_separator == DirectorySeparator::PlatformDefault) { +#ifdef _WIN32 + type1 = '/'; + type2 = '\\'; +#endif + } + + std::replace(path.begin(), path.end(), type1, type2); + path.erase(std::unique(path.begin(), path.end(), + [type2](char c1, char c2) { return c1 == type2 && c2 == type2; }), + path.end()); + return std::string(RemoveTrailingSlash(path)); +} + IOFile::IOFile() {} IOFile::IOFile(const std::string& filename, const char openmode[], int flags) { diff --git a/src/common/file_util.h b/src/common/file_util.h index 45917423ad..09c3cb6f05 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include "common/common_types.h" @@ -166,6 +167,41 @@ std::size_t ReadFileToString(bool text_file, const std::string& filename, std::s void SplitFilename83(const std::string& filename, std::array& short_name, std::array& extension); +// Splits the path on '/' or '\' and put the components into a vector +// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" } +std::vector SplitPathComponents(std::string_view filename); + +// Gets all of the text up to the last '/' or '\' in the path. +std::string_view GetParentPath(std::string_view path); + +// Gets all of the text after the first '/' or '\' in the path. +std::string_view GetPathWithoutTop(std::string_view path); + +// Gets the filename of the path +std::string_view GetFilename(std::string_view path); + +// Gets the extension of the filename +std::string_view GetExtensionFromFilename(std::string_view name); + +// Removes the final '/' or '\' if one exists +std::string_view RemoveTrailingSlash(std::string_view path); + +// Creates a new vector containing indices [first, last) from the original. +template +std::vector SliceVector(const std::vector& vector, std::size_t first, std::size_t last) { + if (first >= last) + return {}; + last = std::min(last, vector.size()); + return std::vector(vector.begin() + first, vector.begin() + first + last); +} + +enum class DirectorySeparator { ForwardSlash, BackwardSlash, PlatformDefault }; + +// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\' +// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows +std::string SanitizePath(std::string_view path, + DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash); + // simple wrapper for cstdlib file functions to // hopefully will make error checking easier // and make forgetting an fclose() harder