/* * 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 #include 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 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 FsUtils::FilesWithExtension(const std::filesystem::path& dir, std::string ext) { std::vector 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; } }