Browse Source

file_util: Implement `RecursivelyCreateDir`

With this, we no longer require `std::filesystem` support to create the
save file directory when it does not exist.

Note that by default the save file directory exists because it is
created by SDL, so this is mostly useful on platforms that override the
save directory or use `UNPACKED_SAVES`.
pull/5874/head
Gleb Mazovetskiy 3 years ago
parent
commit
f2cb9f0fa0
  1. 9
      Packaging/windows/CMakePresets.json
  2. 11
      Source/mpq/mpq_writer.cpp
  3. 10
      Source/options.cpp
  4. 122
      Source/utils/file_util.cpp
  5. 19
      Source/utils/file_util.h
  6. 8
      Source/utils/paths.h
  7. 21
      test/file_util_test.cpp

9
Packaging/windows/CMakePresets.json

@ -74,15 +74,18 @@
"testPresets": [ "testPresets": [
{ {
"name": "ninja-vcpkg-debug", "name": "ninja-vcpkg-debug",
"configurePreset": "ninja-vcpkg-debug" "configurePreset": "ninja-vcpkg-debug",
"output": {"outputOnFailure": true}
}, },
{ {
"name": "ninja-vcpkg-relwithdebinfo", "name": "ninja-vcpkg-relwithdebinfo",
"configurePreset": "ninja-vcpkg-relwithdebinfo" "configurePreset": "ninja-vcpkg-relwithdebinfo",
"output": {"outputOnFailure": true}
}, },
{ {
"name": "ninja-vcpkg-discord-relwithdebinfo", "name": "ninja-vcpkg-discord-relwithdebinfo",
"configurePreset": "ninja-vcpkg-discord-relwithdebinfo" "configurePreset": "ninja-vcpkg-discord-relwithdebinfo",
"output": {"outputOnFailure": true}
} }
] ]
} }

11
Source/mpq/mpq_writer.cpp

@ -88,15 +88,8 @@ bool IsUnallocatedBlock(const MpqBlockEntry *block)
MpqWriter::MpqWriter(const char *path) MpqWriter::MpqWriter(const char *path)
{ {
#ifdef DVL_HAS_FILESYSTEM const std::string dir = std::string(Dirname(path));
{ RecursivelyCreateDir(dir.c_str());
std::error_code error;
std::filesystem::create_directories(std::filesystem::path(path).parent_path(), error);
if (error) {
LogError("failed to create directory: {}", error.message());
}
}
#endif
LogVerbose("Opening {}", path); LogVerbose("Opening {}", path);
std::string error; std::string error;
bool exists = FileExists(path); bool exists = FileExists(path);

10
Source/options.cpp

@ -236,15 +236,7 @@ void SaveIni()
{ {
if (!IniChanged) if (!IniChanged)
return; return;
#ifdef DVL_HAS_FILESYSTEM RecursivelyCreateDir(paths::ConfigPath().c_str());
{
std::error_code error;
std::filesystem::create_directories(paths::ConfigPath(), error);
if (error) {
LogError("failed to create directory: {}", error.message());
}
}
#endif
const std::string iniPath = GetIniPath(); const std::string iniPath = GetIniPath();
FILE *file = OpenFile(iniPath.c_str(), "wb"); FILE *file = OpenFile(iniPath.c_str(), "wb");
if (file != nullptr) { if (file != nullptr) {

122
Source/utils/file_util.cpp

@ -5,6 +5,7 @@
#include <algorithm> #include <algorithm>
#include <string> #include <string>
#include <vector>
#include <SDL.h> #include <SDL.h>
@ -38,6 +39,13 @@
#include <copyfile.h> #include <copyfile.h>
#endif #endif
// We include sys/stat.h for mkdir where available.
#if !defined(DVL_HAS_FILESYSTEM) && defined(__has_include) && !defined(_WIN32)
#if __has_include(<sys/stat.h>)
#include <sys/stat.h>
#endif
#endif
namespace devilution { namespace devilution {
#if (defined(_WIN64) || defined(_WIN32)) && !defined(NXDK) #if (defined(_WIN64) || defined(_WIN32)) && !defined(NXDK)
@ -55,6 +63,18 @@ std::unique_ptr<wchar_t[]> ToWideChar(string_view path)
} }
#endif #endif
string_view Dirname(string_view path)
{
while (path.size() > 1 && path.back() == DirectorySeparator)
path.remove_suffix(1);
if (path.size() == 1 && path.back() == DirectorySeparator)
return DIRECTORY_SEPARATOR_STR;
const size_t sep = path.find_last_of(DIRECTORY_SEPARATOR_STR);
if (sep == string_view::npos)
return ".";
return string_view { path.data(), sep };
}
bool FileExists(const char *path) bool FileExists(const char *path)
{ {
#if defined(_WIN64) || defined(_WIN32) #if defined(_WIN64) || defined(_WIN32)
@ -95,9 +115,10 @@ bool FileExists(const char *path)
#endif #endif
} }
bool FileExistsAndIsWriteable(const char *path)
{
#if defined(_WIN64) || defined(_WIN32) #if defined(_WIN64) || defined(_WIN32)
namespace {
DWORD WindowsGetFileAttributes(const char *path)
{
#if defined(NXDK) #if defined(NXDK)
const DWORD attr = ::GetFileAttributesA(path); const DWORD attr = ::GetFileAttributesA(path);
#else #else
@ -108,8 +129,7 @@ bool FileExistsAndIsWriteable(const char *path)
} }
const DWORD attr = ::GetFileAttributesW(&pathUtf16[0]); const DWORD attr = ::GetFileAttributesW(&pathUtf16[0]);
#endif #endif
const bool exists = attr != INVALID_FILE_ATTRIBUTES; if (attr == INVALID_FILE_ATTRIBUTES) {
if (!exists) {
if (::GetLastError() == ERROR_FILE_NOT_FOUND || ::GetLastError() == ERROR_PATH_NOT_FOUND) { if (::GetLastError() == ERROR_FILE_NOT_FOUND || ::GetLastError() == ERROR_PATH_NOT_FOUND) {
::SetLastError(ERROR_SUCCESS); ::SetLastError(ERROR_SUCCESS);
} else { } else {
@ -120,7 +140,31 @@ bool FileExistsAndIsWriteable(const char *path)
#endif #endif
} }
} }
return exists && (attr & FILE_ATTRIBUTE_READONLY) == 0; return attr;
}
} // namespace
#endif
bool DirectoryExists(const char *path)
{
#if defined(_WIN64) || defined(_WIN32)
const DWORD attr = WindowsGetFileAttributes(path);
return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0;
#elif defined(DVL_HAS_FILESYSTEM)
std::error_code error;
return std::filesystem::is_directory(path, error);
#elif (_POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)) && !defined(__ANDROID__)
struct ::stat statResult;
return ::stat(path, &statResult) == 0 && S_ISDIR(statResult.st_mode);
#endif
}
bool FileExistsAndIsWriteable(const char *path)
{
#if defined(_WIN64) || defined(_WIN32)
const DWORD attr = WindowsGetFileAttributes(path);
return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_READONLY) == 0;
#elif (_POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)) && !defined(__ANDROID__) #elif (_POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)) && !defined(__ANDROID__)
return ::access(path, W_OK) == 0; return ::access(path, W_OK) == 0;
#else #else
@ -164,6 +208,74 @@ bool GetFileSize(const char *path, std::uintmax_t *size)
#endif #endif
} }
bool CreateDir(const char *path)
{
#ifdef DVL_HAS_FILESYSTEM
std::error_code error;
std::filesystem::create_directory(path, error);
if (error) {
LogError("failed to create directory {}: {}", path, error.message());
return false;
}
return true;
#elif defined(_WIN64) || defined(_WIN32)
#ifdef NXDK
if (::CreateDirectoryA(path, /*lpSecurityAttributes=*/nullptr) != 0)
return true;
if (::GetLastError() == ERROR_ALREADY_EXISTS)
return true;
LogError("failed to create directory {}", path);
return false;
#else
const auto pathUtf16 = ToWideChar(path);
if (pathUtf16 == nullptr) {
LogError("UTF-8 -> UTF-16 conversion error code {}", ::GetLastError());
return false;
}
if (::CreateDirectoryW(&pathUtf16[0], /*lpSecurityAttributes=*/nullptr) != 0)
return true;
if (::GetLastError() == ERROR_ALREADY_EXISTS)
return true;
LogError("failed to create directory {}", path);
return false;
#endif
#else
const int result = ::mkdir(path, 0777);
if (result != 0 && result != EEXIST) {
LogError("failed to create directory {}", path);
return false;
}
return true;
#endif
}
void RecursivelyCreateDir(const char *path)
{
#ifdef DVL_HAS_FILESYSTEM
std::error_code error;
std::filesystem::create_directories(path, error);
if (error) {
LogError("failed to create directory {}: {}", path, error.message());
}
#else
if (DirectoryExists(path))
return;
std::vector<std::string> paths;
std::string cur { path };
do {
paths.push_back(cur);
string_view parent = Dirname(cur);
if (parent == cur)
break;
cur.assign(parent.data(), parent.size());
} while (!DirectoryExists(cur.c_str()));
for (auto it = std::rbegin(paths); it != std::rend(paths); ++it) {
if (!CreateDir(it->c_str()))
return;
}
#endif
}
bool ResizeFile(const char *path, std::uintmax_t size) bool ResizeFile(const char *path, std::uintmax_t size)
{ {
#if defined(_WIN64) || defined(_WIN32) #if defined(_WIN64) || defined(_WIN32)

19
Source/utils/file_util.h

@ -9,6 +9,14 @@
namespace devilution { namespace devilution {
#ifdef _WIN32
constexpr char DirectorySeparator = '\\';
#define DIRECTORY_SEPARATOR_STR "\\"
#else
constexpr char DirectorySeparator = '/';
#define DIRECTORY_SEPARATOR_STR "/"
#endif
bool FileExists(const char *path); bool FileExists(const char *path);
inline bool FileExists(const std::string &str) inline bool FileExists(const std::string &str)
@ -16,8 +24,19 @@ inline bool FileExists(const std::string &str)
return FileExists(str.c_str()); return FileExists(str.c_str());
} }
bool DirectoryExists(const char *path);
string_view Dirname(string_view path);
bool FileExistsAndIsWriteable(const char *path); bool FileExistsAndIsWriteable(const char *path);
bool GetFileSize(const char *path, std::uintmax_t *size); bool GetFileSize(const char *path, std::uintmax_t *size);
/**
* @brief Creates a single directory (non-recursively).
*
* @return True if the directory already existed or has been created sucessfully.
*/
bool CreateDir(const char *path);
void RecursivelyCreateDir(const char *path);
bool ResizeFile(const char *path, std::uintmax_t size); bool ResizeFile(const char *path, std::uintmax_t size);
void RenameFile(const char *from, const char *to); void RenameFile(const char *from, const char *to);
void CopyFileOverwrite(const char *from, const char *to); void CopyFileOverwrite(const char *from, const char *to);

8
Source/utils/paths.h

@ -6,14 +6,6 @@
namespace devilution { namespace devilution {
#ifdef _WIN32
constexpr char DirectorySeparator = '\\';
#define DIRECTORY_SEPARATOR_STR "\\"
#else
constexpr char DirectorySeparator = '/';
#define DIRECTORY_SEPARATOR_STR "/"
#endif
namespace paths { namespace paths {
const std::string &BasePath(); const std::string &BasePath();

21
test/file_util_test.cpp

@ -61,4 +61,25 @@ TEST(FileUtil, ResizeFile)
EXPECT_EQ(size, 30); EXPECT_EQ(size, 30);
} }
TEST(FileUtil, Dirname)
{
EXPECT_EQ(Dirname(""), ".");
EXPECT_EQ(Dirname("abc"), ".");
EXPECT_EQ(Dirname(DIRECTORY_SEPARATOR_STR), DIRECTORY_SEPARATOR_STR);
EXPECT_EQ(Dirname(DIRECTORY_SEPARATOR_STR DIRECTORY_SEPARATOR_STR), DIRECTORY_SEPARATOR_STR);
EXPECT_EQ(Dirname("a" DIRECTORY_SEPARATOR_STR "b"), "a");
EXPECT_EQ(Dirname("a" DIRECTORY_SEPARATOR_STR "b" DIRECTORY_SEPARATOR_STR "c"),
"a" DIRECTORY_SEPARATOR_STR "b");
EXPECT_EQ(Dirname("a" DIRECTORY_SEPARATOR_STR "b" DIRECTORY_SEPARATOR_STR "c"),
"a" DIRECTORY_SEPARATOR_STR "b");
}
TEST(FileUtil, RecursivelyCreateDir)
{
const std::string path = GetTmpPathName(/*suffix=*/"") + DIRECTORY_SEPARATOR_STR
+ "a" + DIRECTORY_SEPARATOR_STR + "b" + DIRECTORY_SEPARATOR_STR + "c";
RecursivelyCreateDir(path.c_str());
EXPECT_TRUE(DirectoryExists(path.c_str()));
}
} // namespace } // namespace

Loading…
Cancel
Save