Files
OpenVulkano/openVulkanoCpp/IO/FsUtils.cpp
2025-06-06 14:34:35 +02:00

263 lines
6.0 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "FsUtils.hpp"
#include "Base/Logger.hpp"
#include "Base/Utils.hpp"
#include <unordered_map>
#include <regex>
namespace fs = std::filesystem;
namespace OpenVulkano
{
bool FsUtils::DeleteEmptyFilesAndDirs(const std::filesystem::path& dir, const bool recursive)
{
bool deleted = false;
for (auto& p: fs::directory_iterator(dir))
{
if (fs::is_directory(p))
{
if (recursive)
{
deleted |= (DeleteEmptyFilesAndDirs(p, recursive));
}
if (fs::is_empty(p))
{
fs::remove(p);
Logger::FILESYS->info("Deleted empty directory: {}", p.path().string());
deleted = true;
}
}
else if (fs::is_empty(p))
{
fs::remove(p);
Logger::FILESYS->info("Deleted empty file: {}", p.path().string());
deleted = true;
}
}
return deleted;
}
bool FsUtils::DeleteEmptyDirs(const std::filesystem::path& dir, const bool recursive)
{
bool deleted = false;
for (auto& p: fs::directory_iterator(dir))
{
if (fs::is_directory(p))
{
if (recursive)
{
deleted |= (DeleteEmptyDirs(p, recursive));
}
if (fs::is_empty(p))
{
fs::remove(p);
Logger::FILESYS->info("Deleted empty directory: {}", p.path().string());
deleted = true;
}
}
}
return deleted;
}
bool FsUtils::DeleteEmptySubTrees(const std::filesystem::path& dir)
{
bool deleted = false;
for (auto& p: fs::directory_iterator(dir))
{
if (fs::is_directory(p))
{
if (IsTreeEmpty(p))
{
fs::remove_all(p);
deleted = true;
}
}
}
return deleted;
}
bool FsUtils::IsTreeEmpty(const std::filesystem::path& dir)
{
for (auto& p: fs::directory_iterator(dir))
{
if (fs::is_directory(p))
{
if (!IsTreeEmpty(p))
{
return false;
}
}
else
{
return false;
}
}
return true;
}
ByteSize FsUtils::GetDirSize(const std::filesystem::path& dir)
{
ByteSize size = 0;
for (auto& p: fs::directory_iterator(dir))
{
if (fs::is_directory(p))
{
size += GetDirSize(p);
}
else
{
size += p.file_size();
}
}
return size;
}
FsCount FsUtils::GetDirFileAndDirCount(const std::filesystem::path& dir)
{
FsCount count;
for (auto& p: fs::directory_iterator(dir))
{
if (fs::is_directory(p))
{
count += GetDirFileAndDirCount(p);
count.dirCount++;
}
else
{
count.fileCount++;
}
}
return count;
}
std::string FsUtils::ToValidFileName(std::string str)
{
// Map of characters to replace with similar looking ones
static const std::unordered_map<char, std::string> replacements = {
{'<', ""}, // Use full-width less than
{'>', ""}, // Use full-width greater than
{':', ""}, // Use modifier letter colon
{'"', ""}, // Use full-width quotation mark
{'/', ""}, // Use fraction slash
{'\\', ""}, // Use big reverse solidus
{'|', ""}, // Use full-width vertical line
{'?', ""}, // Use full-width question mark
{'*', ""}, // Use asterisk operator
};
// Characters to remove (control characters and others that might cause issues)
static const std::regex remove_pattern("[\\x00-\\x1F\\x7F~`!@#$%^&(){}\\[\\]=+,;]");
// Maximum length for many filesystems
static const size_t MAX_LENGTH = 255;
// Replace problematic characters with similar looking ones
for (const auto& [key, value] : replacements) {
size_t pos = 0;
while ((pos = str.find(key, pos)) != std::string::npos) {
str.replace(pos, 1, value);
pos += value.length();
}
}
// Remove other invalid characters
str = std::regex_replace(str, remove_pattern, "");
// Trim leading/trailing spaces and periods
str = std::regex_replace(str, std::regex("^[\\.\\s]+"), "");
str = std::regex_replace(str, std::regex("[\\.\\s]+$"), "");
// Replace multiple spaces with a single space
str = std::regex_replace(str, std::regex("\\s+"), " ");
// Ensure the string isn't empty after sanitization
if (str.empty()) {
str = "unnamed_file";
}
// Truncate if too long, being careful not to cut in the middle of a UTF-8 character
if (str.length() > MAX_LENGTH) {
size_t len = MAX_LENGTH;
while (len > 0 && (str[len] & 0xC0) == 0x80) {
--len;
}
str = str.substr(0, len);
}
return str;
}
std::vector<std::filesystem::path> FsUtils::FilesWithExtension(const std::filesystem::path& dir, std::string ext)
{
std::vector<std::filesystem::path> files;
if (!fs::exists(dir) || !fs::is_directory(dir))
{
Logger::FILESYS->warn("Directory does not exist or is not a directory: {}", dir.string());
return files;
}
if (!ext.empty() && ext[0] != '.') ext = "." + ext;
Utils::ToLower(ext);
try
{
for (const auto& entry : fs::directory_iterator(dir))
{
std::string fExt = entry.path().extension().string();
Utils::ToLower(fExt);
if (fs::is_regular_file(entry) && fExt == ext)
{
files.push_back(entry.path());
}
}
}
catch (const fs::filesystem_error& e)
{
Logger::FILESYS->error("Filesystem error when searching for files: {}", e.what());
}
return files;
}
std::string FsUtils::EnsureExtension(const std::string& fileName, const std::string_view& extension)
{
if (!extension.empty() && !Utils::EndsWith(fileName, extension))
{
std::string name;
name.reserve(fileName.length() + 2 + extension.length());
name += fileName;
if (extension[0] != '.') name += ".";
name += std::string(extension);
return name;
}
return fileName;
}
bool FsUtils::FileContains(const std::filesystem::path& path, const std::string_view& searchString)
{
std::ifstream file(path);
if (!file.is_open())
{
Logger::FILESYS->error("Could not open file: {}", path);
return false;
}
std::string line;
while (std::getline(file, line))
{
if (line.find(searchString) != std::string::npos)
{
return true;
}
}
return false;
}
}