ES-DE/es-core/src/utils/FileSystemUtil.cpp

1175 lines
40 KiB
C++
Raw Normal View History

// SPDX-License-Identifier: MIT
2020-06-22 15:27:53 +00:00
//
// ES-DE
2020-06-22 15:27:53 +00:00
// FileSystemUtil.cpp
//
// Low-level filesystem functions.
// Resolve relative paths, resolve symlinks, create directories,
// remove files etc.
2020-06-22 15:27:53 +00:00
//
#if defined(__ANDROID__)
#include "utils/PlatformUtilAndroid.h"
#include <SDL2/SDL_system.h>
2021-09-23 15:54:27 +00:00
#endif
#if defined(__APPLE__)
#define _DARWIN_USE_64_BIT_INODE
#endif
#include "utils/FileSystemUtil.h"
#include "Log.h"
#include "utils/PlatformUtil.h"
#include "utils/StringUtil.h"
#include <fstream>
#include <regex>
#include <string>
#include <sys/stat.h>
#if defined(_WIN64)
#include <Windows.h>
#include <direct.h>
2020-06-22 15:27:53 +00:00
#else
#include <dirent.h>
#include <unistd.h>
2020-06-22 15:27:53 +00:00
#endif
// For Unix systems, set the install prefix as defined via CMAKE_INSTALL_PREFIX when CMake was run.
// If not defined, the default prefix '/usr' will be used on Linux, '/usr/pkg' on NetBSD and
// '/usr/local' on FreeBSD and OpenBSD. This fallback should not be required though unless the
// build environment is broken.
#if defined(__unix__)
#if defined(ES_INSTALL_PREFIX)
const std::filesystem::path installPrefix {ES_INSTALL_PREFIX};
#else
#if defined(__linux__)
const std::filesystem::path installPrefix {"/usr"};
#elif defined(__NetBSD__)
const std::filesystem::path installPrefix {"/usr/pkg"};
#else
const std::filesystem::path installPrefix {"/usr/local"};
#endif
2020-06-22 15:27:53 +00:00
#endif
#endif
namespace Utils
{
2020-06-22 15:27:53 +00:00
namespace FileSystem
{
2022-12-14 16:30:34 +00:00
static std::string homePath;
static std::filesystem::path homePathSTD;
static std::filesystem::path exePath;
static std::filesystem::path esBinary;
2020-06-22 15:27:53 +00:00
StringList getDirContent(const std::string& path, const bool recursive)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
const std::string& genericPath {getGenericPath(path)};
StringList contentList;
2020-06-22 15:27:53 +00:00
if (!isDirectory(genericPath))
return contentList;
2020-06-22 15:27:53 +00:00
if (recursive) {
#if defined(_WIN64)
for (auto& entry : std::filesystem::recursive_directory_iterator(
Utils::String::stringToWideString(genericPath))) {
contentList.emplace_back(
Utils::String::wideStringToString(entry.path().generic_wstring()));
#else
for (auto& entry : std::filesystem::recursive_directory_iterator(genericPath)) {
contentList.emplace_back(entry.path().generic_string());
#endif
2020-06-22 15:27:53 +00:00
}
}
else {
#if defined(_WIN64)
for (auto& entry : std::filesystem::directory_iterator(
Utils::String::stringToWideString(genericPath))) {
contentList.emplace_back(
Utils::String::wideStringToString(entry.path().generic_wstring()));
#else
for (auto& entry : std::filesystem::directory_iterator(genericPath)) {
contentList.emplace_back(entry.path().generic_string());
#endif
}
2020-06-22 15:27:53 +00:00
}
2020-06-22 15:27:53 +00:00
contentList.sort();
return contentList;
}
FileList getDirContentSTD(const std::filesystem::path& path, const bool recursive)
{
FileList fileList;
if (!isDirectorySTD(path))
return fileList;
if (recursive) {
for (auto& entry : std::filesystem::recursive_directory_iterator(path))
fileList.emplace_back(entry);
}
else {
for (auto& entry : std::filesystem::directory_iterator(path))
fileList.emplace_back(entry);
}
fileList.sort();
return fileList;
}
StringList getMatchingFiles(const std::string& pattern)
{
StringList files;
size_t entry {pattern.find('*')};
if (entry == std::string::npos)
return files;
2022-12-14 16:30:34 +00:00
const std::string& parent {getParent(pattern)};
// Don't allow wildcard matching for the parent directory.
if (entry <= parent.size())
return files;
2022-12-14 16:30:34 +00:00
const StringList& dirContent {getDirContent(parent)};
if (dirContent.size() == 0)
return files;
// Some characters need to be escaped in order for the regular expression to work.
std::string escPattern {Utils::String::replace(pattern, "*", ".*")};
escPattern = Utils::String::replace(escPattern, ")", "\\)");
escPattern = Utils::String::replace(escPattern, "(", "\\(");
escPattern = Utils::String::replace(escPattern, "]", "\\]");
escPattern = Utils::String::replace(escPattern, "[", "\\[");
std::regex expression;
try {
expression = escPattern;
}
catch (...) {
LOG(LogError) << "FileSystemUtil::getMatchingFiles(): Invalid regular expression "
<< "\"" << pattern << "\"";
return files;
}
for (auto& entry : dirContent) {
if (std::regex_match(entry, expression))
files.emplace_back(entry);
}
return files;
}
StringList getPathList(const std::string& path)
2020-06-22 15:27:53 +00:00
{
StringList pathList;
2022-12-14 16:30:34 +00:00
const std::string& genericPath {getGenericPath(path)};
size_t start {0};
size_t end {0};
2020-06-22 15:27:53 +00:00
// Split at '/'
while ((end = genericPath.find("/", start)) != std::string::npos) {
2020-06-22 15:27:53 +00:00
if (end != start)
2022-12-14 16:30:34 +00:00
pathList.emplace_back(std::string {genericPath, start, end - start});
2020-06-22 15:27:53 +00:00
start = end + 1;
}
// Add last folder / file to pathList.
if (start != genericPath.size())
2022-12-14 16:30:34 +00:00
pathList.emplace_back(std::string {genericPath, start, genericPath.size() - start});
2020-06-22 15:27:53 +00:00
return pathList;
}
void setHomePath(const std::string& path)
2020-06-22 15:27:53 +00:00
{
// Set home path.
homePath = getGenericPath(path);
homePathSTD = std::filesystem::path {homePath};
2020-06-22 15:27:53 +00:00
}
std::string getHomePath()
{
// Only construct the homepath once.
if (homePath.length())
return homePath;
#if defined(__ANDROID__)
homePath = FileSystemVariables::sAppDataDirectory;
return homePath;
#endif
#if defined(_WIN64)
2020-06-22 15:27:53 +00:00
// On Windows we need to check HOMEDRIVE and HOMEPATH.
if (!homePath.length()) {
std::wstring envHomeDrive;
std::wstring envHomePath;
#if defined(_MSC_VER) // MSVC compiler.
wchar_t* buffer;
if (!_wdupenv_s(&buffer, nullptr, L"HOMEDRIVE"))
envHomeDrive = buffer;
if (!_wdupenv_s(&buffer, nullptr, L"HOMEPATH"))
envHomePath = buffer;
#else
envHomeDrive = _wgetenv(L"HOMEDRIVE");
envHomePath = _wgetenv(L"HOMEPATH");
#endif
2020-06-22 15:27:53 +00:00
if (envHomeDrive.length() && envHomePath.length())
homePath = getGenericPath(Utils::String::wideStringToString(envHomeDrive) +
"/" + Utils::String::wideStringToString(envHomePath));
2020-06-22 15:27:53 +00:00
}
#else
if (!homePath.length()) {
std::string envHome;
if (getenv("HOME") != nullptr)
envHome = getenv("HOME");
if (envHome.length())
homePath = getGenericPath(envHome);
}
#endif
2020-06-22 15:27:53 +00:00
// No homepath found, fall back to current working directory.
if (!homePath.length())
homePath = std::filesystem::current_path().string();
2020-06-22 15:27:53 +00:00
return homePath;
}
std::filesystem::path getHomePathSTD()
{
// Only construct the homepath once.
if (!homePathSTD.empty())
return homePathSTD;
#if defined(__ANDROID__)
homePathSTD =
std::filesystem::path {getGenericPath(FileSystemVariables::sAppDataDirectory)};
return homePathSTD;
#endif
#if defined(_WIN64)
// On Windows we need to check HOMEDRIVE and HOMEPATH.
std::wstring envHomeDrive;
std::wstring envHomePath;
#if defined(_MSC_VER) // MSVC compiler.
wchar_t* buffer;
if (!_wdupenv_s(&buffer, nullptr, L"HOMEDRIVE"))
envHomeDrive = buffer;
if (!_wdupenv_s(&buffer, nullptr, L"HOMEPATH"))
envHomePath = buffer;
#else
envHomeDrive = _wgetenv(L"HOMEDRIVE");
envHomePath = _wgetenv(L"HOMEPATH");
#endif
if (envHomeDrive.length() && envHomePath.length()) {
homePathSTD = envHomeDrive;
homePathSTD.append(envHomePath);
}
#else
std::string envHome;
if (getenv("HOME") != nullptr)
envHome = getenv("HOME");
if (envHome.length())
homePathSTD = std::filesystem::path {getGenericPath(envHome)};
#endif
// No homepath found, fall back to current working directory.
if (homePathSTD.empty())
homePathSTD = std::filesystem::current_path();
return homePathSTD;
}
std::string getSystemHomeDirectory()
{
#if defined(_WIN64)
// On Windows we need to check HOMEDRIVE and HOMEPATH.
std::wstring envHomeDrive;
std::wstring envHomePath;
#if defined(_MSC_VER) // MSVC compiler.
wchar_t* buffer;
if (!_wdupenv_s(&buffer, nullptr, L"HOMEDRIVE"))
envHomeDrive = buffer;
if (!_wdupenv_s(&buffer, nullptr, L"HOMEPATH"))
envHomePath = buffer;
#else
envHomeDrive = _wgetenv(L"HOMEDRIVE");
envHomePath = _wgetenv(L"HOMEPATH");
#endif
if (envHomeDrive.length() && envHomePath.length())
return getGenericPath(Utils::String::wideStringToString(envHomeDrive) + "/" +
Utils::String::wideStringToString(envHomePath));
#else
std::string envHome;
if (getenv("HOME") != nullptr)
envHome = getenv("HOME");
return envHome;
#endif
return "";
}
std::filesystem::path getAppDataDirectory()
2020-06-22 15:27:53 +00:00
{
#if defined(__ANDROID__)
return getHomePathSTD();
#else
if (FileSystemVariables::sAppDataDirectory.empty()) {
if (Utils::FileSystem::existsSTD(getHomePathSTD().append(".emulationstation"))) {
FileSystemVariables::sAppDataDirectory =
getHomePathSTD().append(".emulationstation");
}
else {
FileSystemVariables::sAppDataDirectory = getHomePathSTD().append("ES-DE");
}
}
return FileSystemVariables::sAppDataDirectory;
#endif
2020-06-22 15:27:53 +00:00
}
std::filesystem::path getInternalAppDataDirectory()
{
#if defined(__ANDROID__)
return AndroidVariables::sExternalDataDirectory;
#else
return std::filesystem::path {};
#endif
}
2020-07-10 17:53:33 +00:00
std::string getPathToBinary(const std::string& executable)
{
#if defined(_WIN64)
2020-07-10 17:53:33 +00:00
return "";
#else
2022-04-26 19:26:25 +00:00
#if defined(FLATPAK_BUILD)
// Ugly hack to compensate for the Flatpak sandbox restrictions. We traverse
// this hardcoded list of paths and use the "which" command to check outside the
// sandbox if the emulator binary exists.
2022-12-14 16:30:34 +00:00
const std::string& pathVariable {
"/var/lib/flatpak/exports/bin:/usr/bin:/usr/local/"
"bin:/usr/local/sbin:/usr/sbin:/sbin:/bin:/usr/games:/usr/"
"local/games:/snap/bin:/var/lib/snapd/snap/bin"};
2022-12-14 16:30:34 +00:00
const std::vector<std::string>& pathList {
Utils::String::delimitedStringToVector(pathVariable, ":")};
// Using a temporary file is the only viable solution I've found to communicate
// between the sandbox and the outside world.
const std::string tempFile {Utils::FileSystem::getAppDataDirectory().string() +
".flatpak_emulator_binary_path.tmp"};
std::string emulatorPath;
for (auto it = pathList.cbegin(); it != pathList.cend(); ++it) {
Utils::Platform::runSystemCommand("flatpak-spawn --host which " + *it + "/" +
executable + " > " + tempFile + " 2>/dev/null");
std::ifstream tempFileStream;
tempFileStream.open(tempFile, std::ios::binary);
getline(tempFileStream, emulatorPath);
tempFileStream.close();
if (emulatorPath != "") {
emulatorPath = getParent(emulatorPath);
break;
}
}
if (exists(tempFile))
removeFile(tempFile);
return emulatorPath;
2022-04-26 19:26:25 +00:00
#else
std::string envPath;
if (getenv("PATH") != nullptr)
envPath = getenv("PATH");
2022-12-14 16:30:34 +00:00
const std::vector<std::string>& pathList {
Utils::String::delimitedStringToVector(envPath, ":")};
2020-07-10 17:53:33 +00:00
std::string pathTest;
for (auto it = pathList.cbegin(); it != pathList.cend(); ++it) {
2020-07-10 17:53:33 +00:00
pathTest = it->c_str() + ("/" + executable);
if (Utils::FileSystem::isRegularFile(pathTest) ||
Utils::FileSystem::isSymlink(pathTest))
2020-07-10 17:53:33 +00:00
return it->c_str();
}
return "";
#endif
#endif
2020-07-10 17:53:33 +00:00
}
void setExePath(const std::string& path)
2020-06-22 15:27:53 +00:00
{
std::string exePathTemp;
2022-12-14 16:30:34 +00:00
constexpr int pathMax {32767};
#if defined(_WIN64)
std::wstring result(pathMax, 0);
if (GetModuleFileNameW(nullptr, &result[0], pathMax) != 0)
exePathTemp = Utils::String::wideStringToString(result);
#else
std::string result(pathMax, 0);
if (readlink("/proc/self/exe", &result[0], pathMax) != -1)
exePathTemp = result;
#endif
exePathTemp.erase(std::find(exePathTemp.begin(), exePathTemp.end(), '\0'),
exePathTemp.end());
esBinary = exePathTemp;
exePath = exePathTemp;
exePath = getCanonicalPathSTD(exePath);
2020-06-22 15:27:53 +00:00
// Fallback to argv[0] if everything else fails.
if (exePath.empty()) {
esBinary = path;
exePath = getCanonicalPathSTD(esBinary);
}
if (isRegularFileSTD(exePath))
exePath = exePath.parent_path();
#if defined(APPIMAGE_BUILD)
// We need to check that the APPIMAGE variable is available as the APPIMAGE_BUILD
// build flag could have been passed without running as an actual AppImage.
if (getenv("APPIMAGE") != nullptr)
esBinary = std::filesystem::path {getenv("APPIMAGE")};
#endif
2020-06-22 15:27:53 +00:00
}
std::string getExePath()
{
// Return executable path.
return exePath.string();
}
std::filesystem::path getExePathSTD()
2020-06-22 15:27:53 +00:00
{
// Return executable path.
2020-06-22 15:27:53 +00:00
return exePath;
}
std::string getEsBinary()
{
// Return the absolute path to the ES-DE binary.
return esBinary.string();
}
std::filesystem::path getEsBinarySTD()
{
// Return the absolute path to the ES-DE binary.
return esBinary;
}
std::filesystem::path getProgramDataPath()
2020-06-22 15:27:53 +00:00
{
#if defined(__ANDROID__)
return AndroidVariables::sInternalDataDirectory;
#elif defined(__unix__)
return std::filesystem::path {installPrefix}.append("share").append("emulationstation");
#else
return std::filesystem::path {};
#endif
2020-06-22 15:27:53 +00:00
}
std::string getPreferredPath(const std::string& path)
2020-06-22 15:27:53 +00:00
{
#if defined(_WIN64)
2022-12-14 16:30:34 +00:00
std::string preferredPath {path};
size_t offset {std::string::npos};
2020-06-22 15:27:53 +00:00
// Convert '/' to '\\'
while ((offset = preferredPath.find('/')) != std::string::npos)
preferredPath.replace(offset, 1, "\\");
2022-12-14 16:30:34 +00:00
#else
const std::string& preferredPath {path};
#endif
return preferredPath;
2020-06-22 15:27:53 +00:00
}
std::string getGenericPath(const std::string& path)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
std::string genericPath {path};
size_t offset {std::string::npos};
2020-06-22 15:27:53 +00:00
// Remove "\\\\?\\"
if ((genericPath.find("\\\\?\\")) == 0)
genericPath.erase(0, 4);
2020-06-22 15:27:53 +00:00
// Convert '\\' to '/'
while ((offset = genericPath.find('\\')) != std::string::npos)
genericPath.replace(offset, 1, "/");
2020-06-22 15:27:53 +00:00
// Remove double '/'
while ((offset = genericPath.find("//")) != std::string::npos)
genericPath.erase(offset, 1);
2020-06-22 15:27:53 +00:00
// Remove trailing '/' when the path is more than a simple '/'
while (genericPath.length() > 1 &&
((offset = genericPath.find_last_of('/')) == (genericPath.length() - 1)))
genericPath.erase(offset, 1);
2020-06-22 15:27:53 +00:00
return genericPath;
2020-06-22 15:27:53 +00:00
}
std::string getEscapedPath(const std::string& path)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
std::string escapedPath {getGenericPath(path)};
2020-06-22 15:27:53 +00:00
#if defined(_WIN64)
2020-06-22 15:27:53 +00:00
// Windows escapes stuff by just putting everything in quotes.
if (escapedPath.find(" ") != std::string::npos)
return '"' + getPreferredPath(escapedPath) + '"';
else
return getPreferredPath(escapedPath);
#else
2020-06-22 15:27:53 +00:00
// Insert a backslash before most characters that would mess up a bash path.
2022-12-14 16:30:34 +00:00
const char* invalidChars {"\\ '\"!$^&*(){}[]?;<>"};
const char* invalidChar {invalidChars};
2020-06-22 15:27:53 +00:00
while (*invalidChar) {
2022-12-14 16:30:34 +00:00
size_t start {0};
size_t offset {0};
2020-06-22 15:27:53 +00:00
while ((offset = escapedPath.find(*invalidChar, start)) != std::string::npos) {
2020-06-22 15:27:53 +00:00
start = offset + 1;
if ((offset == 0) || (escapedPath[offset - 1] != '\\')) {
escapedPath.insert(offset, 1, '\\');
++start;
2020-06-22 15:27:53 +00:00
}
}
++invalidChar;
2020-06-22 15:27:53 +00:00
}
return escapedPath;
#endif
2020-06-22 15:27:53 +00:00
}
std::string getCanonicalPath(const std::string& path)
2020-06-22 15:27:53 +00:00
{
if (path.empty())
return "";
2020-06-22 15:27:53 +00:00
// Hack for builtin resources.
if ((path[0] == ':') && (path[1] == '/'))
return path;
2020-06-22 15:27:53 +00:00
2022-12-14 16:30:34 +00:00
std::string canonicalPath {exists(path) ? getAbsolutePath(path) : getGenericPath(path)};
2020-06-22 15:27:53 +00:00
// Cleanup path.
2022-12-14 16:30:34 +00:00
bool scan {true};
2020-06-22 15:27:53 +00:00
while (scan) {
2022-12-14 16:30:34 +00:00
const StringList& pathList {getPathList(canonicalPath)};
2020-06-22 15:27:53 +00:00
canonicalPath.clear();
2020-06-22 15:27:53 +00:00
scan = false;
2022-12-14 16:30:34 +00:00
for (StringList::const_iterator it {pathList.cbegin()}; it != pathList.cend();
++it) {
2020-06-22 15:27:53 +00:00
// Ignore empty.
if ((*it).empty())
continue;
// Remove "/./"
if ((*it) == ".")
continue;
// Resolve "/../"
if ((*it) == "..") {
canonicalPath = getParent(canonicalPath);
2020-06-22 15:27:53 +00:00
continue;
}
#if defined(_WIN64)
2020-06-22 15:27:53 +00:00
// Append folder to path.
canonicalPath += (canonicalPath.size() == 0) ? (*it) : ("/" + (*it));
#else
2020-06-22 15:27:53 +00:00
// Append folder to path.
canonicalPath += ("/" + (*it));
#endif
2020-06-22 15:27:53 +00:00
if (isSymlink(canonicalPath)) {
2022-12-14 16:30:34 +00:00
const std::string& resolved {resolveSymlink(canonicalPath)};
2020-06-22 15:27:53 +00:00
if (resolved.empty())
return "";
if (isAbsolute(resolved))
canonicalPath = resolved;
2020-06-22 15:27:53 +00:00
else
canonicalPath = getParent(canonicalPath) + "/" + resolved;
2020-06-22 15:27:53 +00:00
for (++it; it != pathList.cend(); ++it)
canonicalPath += (canonicalPath.size() == 0) ? (*it) : ("/" + (*it));
2020-06-22 15:27:53 +00:00
scan = true;
break;
}
}
}
return canonicalPath;
2020-06-22 15:27:53 +00:00
}
std::filesystem::path getCanonicalPathSTD(const std::filesystem::path& path)
{
if (path.empty())
return path;
// Hack for builtin resources.
if ((path.string()[0] == ':') && (path.string()[1] == '/'))
return path;
return std::filesystem::canonical(path);
}
std::string getAbsolutePath(const std::string& path, const std::string& base)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
const std::string& absolutePath {getGenericPath(path)};
const std::string& baseVar {isAbsolute(base) ? getGenericPath(base) :
getAbsolutePath(base)};
2020-06-22 15:27:53 +00:00
return isAbsolute(absolutePath) ? absolutePath :
getGenericPath(baseVar + "/" + absolutePath);
2020-06-22 15:27:53 +00:00
}
std::string getParent(const std::string& path)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
std::string genericPath {getGenericPath(path)};
size_t offset {std::string::npos};
2020-06-22 15:27:53 +00:00
// Find last '/' and erase it.
if ((offset = genericPath.find_last_of('/')) != std::string::npos)
return genericPath.erase(offset);
2020-06-22 15:27:53 +00:00
// No parent found.
return genericPath;
2020-06-22 15:27:53 +00:00
}
std::string getFileName(const std::string& path)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
const std::string& genericPath {getGenericPath(path)};
size_t offset {std::string::npos};
2020-06-22 15:27:53 +00:00
// Find last '/' and return the filename.
if ((offset = genericPath.find_last_of('/')) != std::string::npos)
return ((genericPath[offset + 1] == 0) ? "." :
2022-12-14 16:30:34 +00:00
std::string {genericPath, offset + 1});
2020-06-22 15:27:53 +00:00
// No '/' found, entire path is a filename.
return genericPath;
2020-06-22 15:27:53 +00:00
}
std::filesystem::path getFileNameSTD(const std::filesystem::path& path)
{
return path.filename();
}
std::string getStem(const std::string& path)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
std::string fileName {getFileName(path)};
size_t offset {std::string::npos};
2020-06-22 15:27:53 +00:00
// Empty fileName.
if (fileName == ".")
return fileName;
if (!Utils::FileSystem::isDirectory(path)) {
// Find last '.' and erase the extension.
if ((offset = fileName.find_last_of('.')) != std::string::npos)
return fileName.erase(offset);
}
2020-06-22 15:27:53 +00:00
// No '.' found, filename has no extension.
return fileName;
}
std::string getExtension(const std::string& path)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
const std::string& fileName {getFileName(path)};
size_t offset {std::string::npos};
2020-06-22 15:27:53 +00:00
// Empty fileName.
if (fileName == ".")
return fileName;
// Find last '.' and return the extension.
if ((offset = fileName.find_last_of('.')) != std::string::npos)
2022-12-14 16:30:34 +00:00
return std::string {fileName, offset};
2020-06-22 15:27:53 +00:00
// No '.' found, filename has no extension.
return ".";
}
long getFileSize(const std::filesystem::path& path)
{
try {
#if defined(_WIN64)
return static_cast<long>(std::filesystem::file_size(
Utils::String::stringToWideString(path.generic_string())));
#else
return static_cast<long>(std::filesystem::file_size(path));
#endif
}
catch (std::filesystem::filesystem_error& error) {
LOG(LogError) << "FileSystemUtil::getFileSize(): " << error.what();
return -1;
}
}
std::string expandHomePath(const std::string& path)
{
// Expand home path if ~ is used.
return Utils::String::replace(path, "~", Utils::FileSystem::getHomePath());
}
std::string resolveRelativePath(const std::string& path,
const std::string& relativeTo,
const bool allowHome)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
const std::string& genericPath {getGenericPath(path)};
const std::string& relativeToVar {isDirectory(relativeTo) ? getGenericPath(relativeTo) :
getParent(relativeTo)};
2020-06-22 15:27:53 +00:00
// Nothing to resolve.
if (!genericPath.length())
return genericPath;
2020-06-22 15:27:53 +00:00
// Replace '.' with relativeToVar.
if ((genericPath[0] == '.') && (genericPath[1] == '/'))
return (relativeToVar + &(genericPath[1]));
2020-06-22 15:27:53 +00:00
// Replace '~' with homePath.
if (allowHome && (genericPath[0] == '~') && (genericPath[1] == '/'))
return (getHomePath() + &(genericPath[1]));
2020-06-22 15:27:53 +00:00
// Nothing to resolve.
return genericPath;
2020-06-22 15:27:53 +00:00
}
std::string createRelativePath(const std::string& path,
const std::string& relativeTo,
const bool allowHome)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
bool contains {false};
std::string relativePath {removeCommonPath(path, relativeTo, contains)};
2020-06-22 15:27:53 +00:00
if (contains)
return ("./" + relativePath);
2020-06-22 15:27:53 +00:00
if (allowHome) {
relativePath = removeCommonPath(path, getHomePath(), contains);
2020-06-22 15:27:53 +00:00
if (contains)
return ("~/" + relativePath);
2020-06-22 15:27:53 +00:00
}
return relativePath;
2020-06-22 15:27:53 +00:00
}
std::string removeCommonPath(const std::string& path,
const std::string& commonArg,
bool& contains)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
const std::string& genericPath {getGenericPath(path)};
const std::string& common {isDirectory(commonArg) ? getGenericPath(commonArg) :
getParent(commonArg)};
2020-06-22 15:27:53 +00:00
if (genericPath.find(common) == 0) {
contains = true;
return genericPath.substr(common.length() + 1);
2020-06-22 15:27:53 +00:00
}
contains = false;
return genericPath;
2020-06-22 15:27:53 +00:00
}
std::string resolveSymlink(const std::string& path)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
const std::string& genericPath {getGenericPath(path)};
2020-06-22 15:27:53 +00:00
std::string resolved;
#if defined(_WIN64)
std::wstring resolvedW;
HANDLE hFile = CreateFileW(Utils::String::stringToWideString(genericPath).c_str(),
FILE_READ_ATTRIBUTES, FILE_SHARE_READ, 0, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hFile != INVALID_HANDLE_VALUE) {
resolvedW.resize(
GetFinalPathNameByHandleW(hFile, nullptr, 0, FILE_NAME_NORMALIZED) + 1);
if (GetFinalPathNameByHandleW(hFile, const_cast<LPWSTR>(resolvedW.data()),
static_cast<DWORD>(resolvedW.size()),
FILE_NAME_NORMALIZED) > 0) {
resolvedW.resize(resolvedW.size() - 2);
resolved = getGenericPath(Utils::String::wideStringToString(resolvedW));
}
CloseHandle(hFile);
}
#else
2020-06-22 15:27:53 +00:00
struct stat info;
// Check if lstat succeeded.
if (lstat(genericPath.c_str(), &info) == 0) {
2020-06-22 15:27:53 +00:00
resolved.resize(info.st_size);
if (readlink(genericPath.c_str(), const_cast<char*>(resolved.data()),
resolved.size()) > 0)
2020-06-22 15:27:53 +00:00
resolved = getGenericPath(resolved);
}
#endif
2020-06-22 15:27:53 +00:00
return resolved;
}
bool copyFile(const std::string& sourcePath,
const std::string& destinationPath,
bool overwrite)
{
if (!exists(sourcePath)) {
LOG(LogError) << "Can't copy file, source file does not exist:";
LOG(LogError) << sourcePath;
return true;
}
if (isDirectory(destinationPath)) {
LOG(LogError) << "Destination file is actually a directory:";
LOG(LogError) << destinationPath;
return true;
}
if (!overwrite && exists(destinationPath)) {
LOG(LogError) << "Destination file exists and the overwrite flag "
"has not been set";
return true;
}
#if defined(_WIN64)
2022-12-14 16:30:34 +00:00
std::ifstream sourceFile {Utils::String::stringToWideString(sourcePath).c_str(),
std::ios::binary};
#else
2022-12-14 16:30:34 +00:00
std::ifstream sourceFile {sourcePath, std::ios::binary};
#endif
if (sourceFile.fail()) {
LOG(LogError) << "Couldn't read from source file \"" << sourcePath
<< "\", permission problems?";
sourceFile.close();
return true;
}
#if defined(_WIN64)
2022-12-14 16:30:34 +00:00
std::ofstream targetFile {Utils::String::stringToWideString(destinationPath).c_str(),
std::ios::binary};
#else
2022-12-14 16:30:34 +00:00
std::ofstream targetFile {destinationPath, std::ios::binary};
#endif
if (targetFile.fail()) {
LOG(LogError) << "Couldn't write to target file \"" << destinationPath
<< "\", permission problems?";
targetFile.close();
return true;
}
targetFile << sourceFile.rdbuf();
sourceFile.close();
targetFile.close();
return false;
}
bool renameFile(const std::string& sourcePath,
const std::string& destinationPath,
bool overwrite)
{
// Don't print any error message for a missing source file as Log will use this
// function when initializing the logging. It would always generate an error in
// case it's the first application start (as an old log file would then not exist).
if (!exists(sourcePath)) {
return true;
}
if (isDirectory(destinationPath)) {
LOG(LogError) << "Destination file is actually a directory:";
LOG(LogError) << destinationPath;
return true;
}
if (!overwrite && exists(destinationPath)) {
LOG(LogError) << "Destination file exists and the overwrite flag has not been set";
return true;
}
#if defined(_WIN64)
return _wrename(Utils::String::stringToWideString(sourcePath).c_str(),
Utils::String::stringToWideString(destinationPath).c_str());
#else
return std::rename(sourcePath.c_str(), destinationPath.c_str());
#endif
}
bool createEmptyFile(const std::filesystem::path& path)
{
const std::filesystem::path cleanPath {path.lexically_normal().make_preferred()};
if (existsSTD(path)) {
LOG(LogError) << "Couldn't create target file \"" << cleanPath.string()
<< "\" as it already exists";
return false;
}
#if defined(_WIN64)
std::ofstream targetFile {Utils::String::stringToWideString(cleanPath.string()).c_str(),
std::ios::binary};
#else
std::ofstream targetFile {cleanPath, std::ios::binary};
#endif
if (targetFile.fail()) {
LOG(LogError) << "Couldn't create target file \"" << cleanPath.string()
<< "\", permission problems?";
targetFile.close();
return false;
}
targetFile.close();
return true;
}
bool removeFile(const std::string& path)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
const std::string& genericPath {getGenericPath(path)};
try {
#if defined(_WIN64)
return std::filesystem::remove(Utils::String::stringToWideString(genericPath));
#else
return std::filesystem::remove(genericPath);
#endif
}
catch (std::filesystem::filesystem_error& error) {
LOG(LogError) << "FileSystemUtil::removeFile(): " << error.what();
return false;
}
2020-06-22 15:27:53 +00:00
}
bool removeDirectory(const std::string& path, bool recursive)
{
const std::string& genericPath {getGenericPath(path)};
try {
#if defined(_WIN64)
if (recursive)
return std::filesystem::remove_all(
Utils::String::stringToWideString(genericPath));
else
return std::filesystem::remove(Utils::String::stringToWideString(genericPath));
#else
if (recursive)
return std::filesystem::remove_all(genericPath);
else
return std::filesystem::remove(genericPath);
#endif
}
catch (std::filesystem::filesystem_error& error) {
LOG(LogError) << "FileSystemUtil::removeDirectory(): " << error.what();
return false;
}
}
bool createDirectory(const std::string& path)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
const std::string& genericPath {getGenericPath(path)};
2020-06-22 15:27:53 +00:00
if (exists(genericPath))
2020-06-22 15:27:53 +00:00
return true;
#if defined(_WIN64)
if (_wmkdir(Utils::String::stringToWideString(genericPath).c_str()) == 0)
return true;
#else
if (mkdir(genericPath.c_str(), 0755) == 0)
2020-06-22 15:27:53 +00:00
return true;
#endif
2020-06-22 15:27:53 +00:00
// Failed to create directory, try to create the parent.
2022-12-14 16:30:34 +00:00
const std::string& parent {getParent(genericPath)};
2020-06-22 15:27:53 +00:00
// Only try to create parent if it's not identical to genericPath.
if (parent != genericPath)
2020-06-22 15:27:53 +00:00
createDirectory(parent);
// Try to create directory again now that the parent should exist.
#if defined(_WIN64)
return (_wmkdir(Utils::String::stringToWideString(genericPath).c_str()) == 0);
#else
return (mkdir(genericPath.c_str(), 0755) == 0);
#endif
2020-06-22 15:27:53 +00:00
}
bool exists(const std::string& path)
2020-06-22 15:27:53 +00:00
{
const std::string& genericPath {getGenericPath(path)};
try {
#if defined(_WIN64)
return std::filesystem::exists(Utils::String::stringToWideString(genericPath));
#else
return std::filesystem::exists(genericPath);
#endif
}
catch (std::filesystem::filesystem_error& error) {
LOG(LogError) << "FileSystemUtil::exists(): " << error.what();
return false;
}
2020-06-22 15:27:53 +00:00
}
bool existsSTD(const std::filesystem::path& path)
{
const std::string& genericPath {getGenericPath(path.string())};
try {
#if defined(_WIN64)
return std::filesystem::exists(Utils::String::stringToWideString(genericPath));
#else
return std::filesystem::exists(genericPath);
#endif
}
catch (std::filesystem::filesystem_error& error) {
LOG(LogError) << "FileSystemUtil::exists(): " << error.what();
return false;
}
}
bool driveExists(const std::string& path)
{
#if defined(_WIN64)
2022-12-14 16:30:34 +00:00
std::string genericPath {getGenericPath(path)};
// Try to add a dot or a backslash and a dot depending on how the drive
// letter was defined by the user.
if (genericPath.length() == 2 && genericPath.at(1) == ':')
genericPath += "\\.";
else if (genericPath.length() == 3 && genericPath.at(1) == ':')
genericPath += ".";
return exists(genericPath);
#else
return false;
#endif
}
bool isAbsolute(const std::string& path)
2020-06-22 15:27:53 +00:00
{
const std::string& genericPath {getGenericPath(path)};
try {
#if defined(_WIN64)
return ((genericPath.size() > 1) && (genericPath[1] == ':'));
#else
return ((genericPath.size() > 0) && (genericPath[0] == '/'));
#endif
}
catch (...) {
return false;
}
2020-06-22 15:27:53 +00:00
}
bool isRegularFile(const std::string& path)
2020-06-22 15:27:53 +00:00
{
const std::string& genericPath {getGenericPath(path)};
try {
#if defined(_WIN64)
return std::filesystem::is_regular_file(
Utils::String::stringToWideString(genericPath));
#else
return std::filesystem::is_regular_file(genericPath);
#endif
}
catch (std::filesystem::filesystem_error& error) {
LOG(LogError) << "FileSystemUtil::isRegularFile(): " << error.what();
return false;
}
2020-06-22 15:27:53 +00:00
}
bool isRegularFileSTD(const std::filesystem::path& path)
{
try {
return std::filesystem::is_regular_file(path);
}
catch (std::filesystem::filesystem_error& error) {
LOG(LogError) << "FileSystemUtil::isRegularFile(): " << error.what();
return false;
}
}
bool isDirectory(const std::string& path)
2020-06-22 15:27:53 +00:00
{
const std::string& genericPath {getGenericPath(path)};
try {
#if defined(_WIN64)
return std::filesystem::is_directory(
Utils::String::stringToWideString(genericPath));
#else
return std::filesystem::is_directory(genericPath);
#endif
}
catch (std::filesystem::filesystem_error& error) {
LOG(LogError) << "FileSystemUtil::isDirectory(): " << error.what();
return false;
}
2020-06-22 15:27:53 +00:00
}
bool isDirectorySTD(const std::filesystem::path& path)
{
try {
return std::filesystem::is_directory(path);
}
catch (std::filesystem::filesystem_error& error) {
LOG(LogError) << "FileSystemUtil::isDirectory(): " << error.what();
return false;
}
}
bool isSymlink(const std::string& path)
2020-06-22 15:27:53 +00:00
{
const std::string& genericPath {getGenericPath(path)};
try {
#if defined(_WIN64)
return std::filesystem::is_symlink(Utils::String::stringToWideString(genericPath));
#else
return std::filesystem::is_symlink(genericPath);
#endif
}
catch (std::filesystem::filesystem_error& error) {
LOG(LogError) << "FileSystemUtil::isSymlink(): " << error.what();
return false;
}
2020-06-22 15:27:53 +00:00
}
bool isSymlinkSTD(const std::filesystem::path& path)
{
try {
return std::filesystem::is_symlink(path);
}
catch (std::filesystem::filesystem_error& error) {
LOG(LogError) << "FileSystemUtil::isSymlink(): " << error.what();
return false;
}
}
bool isHidden(const std::string& path)
2020-06-22 15:27:53 +00:00
{
2022-12-14 16:30:34 +00:00
const std::string& genericPath {getGenericPath(path)};
#if defined(_WIN64)
2020-06-22 15:27:53 +00:00
// Check for hidden attribute.
2022-12-14 16:30:34 +00:00
const DWORD Attributes {
GetFileAttributesW(Utils::String::stringToWideString(genericPath).c_str())};
2020-06-22 15:27:53 +00:00
if ((Attributes != INVALID_FILE_ATTRIBUTES) && (Attributes & FILE_ATTRIBUTE_HIDDEN))
return true;
#endif
2020-06-22 15:27:53 +00:00
// Filenames starting with . are hidden in Linux, but
// we do this check for windows as well.
if (getFileName(genericPath)[0] == '.')
2020-06-22 15:27:53 +00:00
return true;
return false;
}
} // namespace FileSystem
} // namespace Utils