#pragma once #ifndef _WIN32 #include #include #include #include #include #include #include #include #if __linux__ || __APPLE__ extern "C" int rep_closefrom(int lower); #define closefrom rep_closefrom #endif #else #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN 1 #endif #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include #include #include "winsupport.hpp" #endif #include #include #include #include #include #include #include #include #include #include #include #include "FourCC.hpp" #include "athena/Global.hpp" #include "logvisor/logvisor.hpp" #include "xxhash/xxhash.h" #include "SystemChar.hpp" #include "FourCC.hpp" #if defined(__has_feature) #if __has_feature(thread_sanitizer) #define HECL_NO_SANITIZE_THREAD __attribute__((no_sanitize("thread"))) #endif #endif #ifndef HECL_NO_SANITIZE_THREAD #define HECL_NO_SANITIZE_THREAD #endif namespace hecl { namespace Database { class Project; struct DataSpecEntry; } // namespace Database namespace blender { enum class BlendType { None, Mesh, ColMesh, Armature, Actor, Area, World, MapArea, MapUniverse, Frame, PathMesh }; class ANIMOutStream; class Connection; class DataStream; class PyOutStream; class Token; struct Action; struct Actor; struct Armature; struct Bone; struct ColMesh; struct Light; struct MapArea; struct MapUniverse; struct Material; struct Matrix3f; struct Matrix4f; struct Mesh; struct PathMesh; struct PoolSkinIndex; struct World; extern class Token SharedBlenderToken; } // namespace blender extern unsigned VerbosityLevel; extern bool GuiMode; extern logvisor::Module LogModule; std::string WideToUTF8(std::wstring_view src); std::string Char16ToUTF8(std::u16string_view src); std::wstring Char16ToWide(std::u16string_view src); std::wstring UTF8ToWide(std::string_view src); std::u16string UTF8ToChar16(std::string_view src); /* humanize_number port from FreeBSD's libutil */ enum class HNFlags { None = 0, Decimal = 0x01, NoSpace = 0x02, B = 0x04, Divisor1000 = 0x08, IECPrefixes = 0x10 }; ENABLE_BITWISE_ENUM(HNFlags) enum class HNScale { None = 0, AutoScale = 0x20 }; ENABLE_BITWISE_ENUM(HNScale) std::string HumanizeNumber(int64_t quotient, size_t len, const char* suffix, int scale, HNFlags flags); #if HECL_UCS2 class SystemUTF8Conv { std::string m_utf8; public: explicit SystemUTF8Conv(SystemStringView str) : m_utf8(WideToUTF8(str)) {} std::string_view str() const { return m_utf8; } const char* c_str() const { return m_utf8.c_str(); } friend std::string operator+(const SystemUTF8Conv& lhs, std::string_view rhs) { return lhs.m_utf8 + rhs.data(); } friend std::string operator+(std::string_view lhs, const SystemUTF8Conv& rhs) { return std::string(lhs).append(rhs.m_utf8); } }; class SystemStringConv { std::wstring m_sys; public: explicit SystemStringConv(std::string_view str) : m_sys(UTF8ToWide(str)) {} SystemStringView sys_str() const { return m_sys; } const SystemChar* c_str() const { return m_sys.c_str(); } friend std::wstring operator+(const SystemStringConv& lhs, const std::wstring_view rhs) { return lhs.m_sys + rhs.data(); } friend std::wstring operator+(std::wstring_view lhs, const SystemStringConv& rhs) { return std::wstring(lhs).append(rhs.m_sys); } }; inline hecl::SystemString UTF8StringToSysString(std::string_view src) { return UTF8ToWide(src); } #else class SystemUTF8Conv { std::string_view m_utf8; public: explicit SystemUTF8Conv(SystemStringView str) : m_utf8(str) {} std::string_view str() const { return m_utf8; } const char* c_str() const { return m_utf8.data(); } friend std::string operator+(const SystemUTF8Conv& lhs, std::string_view rhs) { return std::string(lhs.m_utf8).append(rhs); } friend std::string operator+(std::string_view lhs, const SystemUTF8Conv& rhs) { return std::string(lhs).append(rhs.m_utf8); } }; class SystemStringConv { std::string_view m_sys; public: explicit SystemStringConv(std::string_view str) : m_sys(str) {} SystemStringView sys_str() const { return m_sys; } const SystemChar* c_str() const { return m_sys.data(); } friend std::string operator+(const SystemStringConv& lhs, std::string_view rhs) { return std::string(lhs.m_sys).append(rhs); } friend std::string operator+(std::string_view lhs, const SystemStringConv& rhs) { return std::string(lhs).append(rhs.m_sys); } }; inline hecl::SystemString UTF8StringToSysString(std::string src) { return src; } #endif void SanitizePath(std::string& path); void SanitizePath(std::wstring& path); inline void Unlink(const SystemChar* file) { #if _WIN32 _wunlink(file); #else unlink(file); #endif } inline void MakeDir(const char* dir) { #if _WIN32 HRESULT err; if (!CreateDirectoryA(dir, NULL)) if ((err = GetLastError()) != ERROR_ALREADY_EXISTS) LogModule.report(logvisor::Fatal, FMT_STRING("MakeDir({})"), dir); #else if (mkdir(dir, 0755)) if (errno != EEXIST) LogModule.report(logvisor::Fatal, FMT_STRING("MakeDir({}): {}"), dir, strerror(errno)); #endif } #if _WIN32 inline void MakeDir(const wchar_t* dir) { HRESULT err; if (!CreateDirectoryW(dir, NULL)) if ((err = GetLastError()) != ERROR_ALREADY_EXISTS) LogModule.report(logvisor::Fatal, FMT_STRING(_SYS_STR("MakeDir({})")), dir); } #endif int RecursiveMakeDir(const SystemChar* dir); inline const SystemChar* GetEnv(const SystemChar* name) { #if WINDOWS_STORE return nullptr; #else #if HECL_UCS2 return _wgetenv(name); #else return getenv(name); #endif #endif } inline SystemChar* Getcwd(SystemChar* buf, int maxlen) { #if HECL_UCS2 return _wgetcwd(buf, maxlen); #else return getcwd(buf, maxlen); #endif } SystemString GetcwdStr(); inline bool IsAbsolute(SystemStringView path) { #if _WIN32 if (path.size() && (path[0] == _SYS_STR('\\') || path[0] == _SYS_STR('/'))) return true; if (path.size() >= 2 && iswalpha(path[0]) && path[1] == _SYS_STR(':')) return true; #else if (path.size() && path[0] == _SYS_STR('/')) return true; #endif return false; } const SystemChar* GetTmpDir(); #if !WINDOWS_STORE int RunProcess(const SystemChar* path, const SystemChar* const args[]); #endif enum class FileLockType { None = 0, Read, Write }; inline FILE* Fopen(const SystemChar* path, const SystemChar* mode, FileLockType lock = FileLockType::None) { #if HECL_UCS2 FILE* fp = _wfopen(path, mode); if (!fp) return nullptr; #else FILE* fp = fopen(path, mode); if (!fp) return nullptr; #endif if (lock != FileLockType::None) { #if _WIN32 OVERLAPPED ov = {}; LockFileEx((HANDLE)(uintptr_t)_fileno(fp), (lock == FileLockType::Write) ? LOCKFILE_EXCLUSIVE_LOCK : 0, 0, 0, 1, &ov); #else if (flock(fileno(fp), ((lock == FileLockType::Write) ? LOCK_EX : LOCK_SH) | LOCK_NB)) LogModule.report(logvisor::Error, FMT_STRING("flock {}: {}"), path, strerror(errno)); #endif } return fp; } struct UniqueFileDeleter { void operator()(FILE* file) const noexcept { std::fclose(file); } }; using UniqueFilePtr = std::unique_ptr; inline UniqueFilePtr FopenUnique(const SystemChar* path, const SystemChar* mode, FileLockType lock = FileLockType::None) { return UniqueFilePtr{Fopen(path, mode, lock)}; } inline int FSeek(FILE* fp, int64_t offset, int whence) { #if _WIN32 return _fseeki64(fp, offset, whence); #elif __APPLE__ || __FreeBSD__ return fseeko(fp, offset, whence); #else return fseeko64(fp, offset, whence); #endif } inline int64_t FTell(FILE* fp) { #if _WIN32 return _ftelli64(fp); #elif __APPLE__ || __FreeBSD__ return ftello(fp); #else return ftello64(fp); #endif } inline int Rename(const SystemChar* oldpath, const SystemChar* newpath) { #if HECL_UCS2 // return _wrename(oldpath, newpath); return MoveFileExW(oldpath, newpath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) == 0; #else return rename(oldpath, newpath); #endif } inline int Stat(const SystemChar* path, Sstat* statOut) { #if HECL_UCS2 size_t pos; for (pos = 0; pos < 3 && path[pos] != L'\0'; ++pos) {} if (pos == 2 && path[1] == L':') { SystemChar fixPath[4] = {path[0], L':', L'/', L'\0'}; return _wstat(fixPath, statOut); } return _wstat(path, statOut); #else return stat(path, statOut); #endif } inline int StrCmp(const SystemChar* str1, const SystemChar* str2) { if (!str1 || !str2) return str1 != str2; #if HECL_UCS2 return wcscmp(str1, str2); #else return strcmp(str1, str2); #endif } inline int StrNCmp(const SystemChar* str1, const SystemChar* str2, size_t count) { if (!str1 || !str2) return str1 != str2; return std::char_traits::compare(str1, str2, count); } inline int StrCaseCmp(const SystemChar* str1, const SystemChar* str2) { if (!str1 || !str2) return str1 != str2; #if HECL_UCS2 return _wcsicmp(str1, str2); #else return strcasecmp(str1, str2); #endif } inline unsigned long StrToUl(const SystemChar* str, SystemChar** endPtr, int base) { #if HECL_UCS2 return wcstoul(str, endPtr, base); #else return strtoul(str, endPtr, base); #endif } inline bool CheckFreeSpace(const SystemChar* path, size_t reqSz) { #if _WIN32 ULARGE_INTEGER freeBytes; wchar_t buf[1024]; wchar_t* end; DWORD ret = GetFullPathNameW(path, 1024, buf, &end); if (!ret || ret > 1024) LogModule.report(logvisor::Fatal, FMT_STRING(_SYS_STR("GetFullPathNameW {}")), path); if (end) end[0] = L'\0'; if (!GetDiskFreeSpaceExW(buf, &freeBytes, nullptr, nullptr)) LogModule.report(logvisor::Fatal, FMT_STRING(_SYS_STR("GetDiskFreeSpaceExW {}: {}")), path, GetLastError()); return reqSz < freeBytes.QuadPart; #else struct statvfs svfs; if (statvfs(path, &svfs)) LogModule.report(logvisor::Fatal, FMT_STRING("statvfs {}: {}"), path, strerror(errno)); return reqSz < svfs.f_frsize * svfs.f_bavail; #endif } inline bool PathRelative(const SystemChar* path) { if (!path || !path[0]) return false; #if _WIN32 && !WINDOWS_STORE return PathIsRelative(path); #else return path[0] != '/'; #endif } inline int ConsoleWidth(bool* ok = nullptr) { int retval = 80; #if _WIN32 #if !WINDOWS_STORE CONSOLE_SCREEN_BUFFER_INFO info; GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); retval = info.dwSize.X; if (ok) *ok = true; #endif #else if (ok) *ok = false; struct winsize w; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) { retval = w.ws_col; if (ok) *ok = true; } #endif if (retval < 10) return 10; return retval; } class MultiProgressPrinter; class ProjectRootPath; using SystemRegex = std::basic_regex; using SystemRegexIterator = std::regex_iterator; using SystemRegexMatch = std::match_results; using SystemViewRegexMatch = std::match_results; using SystemRegexTokenIterator = std::regex_token_iterator; /** * @brief Hash representation used for all storable and comparable objects * * Hashes are used within HECL to avoid redundant storage of objects; * providing a rapid mechanism to compare for equality. */ class Hash { protected: uint64_t hash = 0; public: constexpr Hash() noexcept = default; constexpr Hash(const Hash&) noexcept = default; constexpr Hash(Hash&&) noexcept = default; constexpr Hash(uint64_t hashin) noexcept : hash(hashin) {} explicit Hash(const void* buf, size_t len) noexcept : hash(XXH64(buf, len, 0)) {} explicit Hash(std::string_view str) noexcept : hash(XXH64(str.data(), str.size(), 0)) {} explicit Hash(std::wstring_view str) noexcept : hash(XXH64(str.data(), str.size() * 2, 0)) {} constexpr uint32_t val32() const noexcept { return uint32_t(hash) ^ uint32_t(hash >> 32); } constexpr uint64_t val64() const noexcept { return uint64_t(hash); } constexpr size_t valSizeT() const noexcept { return size_t(hash); } template constexpr T valT() const noexcept; constexpr Hash& operator=(const Hash& other) noexcept = default; constexpr Hash& operator=(Hash&& other) noexcept = default; constexpr bool operator==(const Hash& other) const noexcept { return hash == other.hash; } constexpr bool operator!=(const Hash& other) const noexcept { return !operator==(other); } constexpr bool operator<(const Hash& other) const noexcept { return hash < other.hash; } constexpr bool operator>(const Hash& other) const noexcept { return hash > other.hash; } constexpr bool operator<=(const Hash& other) const noexcept { return hash <= other.hash; } constexpr bool operator>=(const Hash& other) const noexcept { return hash >= other.hash; } constexpr explicit operator bool() const noexcept { return hash != 0; } }; template <> constexpr uint32_t Hash::valT() const noexcept { return val32(); } template <> constexpr uint64_t Hash::valT() const noexcept { return val64(); } /** * @brief Timestamp representation used for comparing modtimes of cooked resources */ class Time final { time_t ts; public: Time() : ts(std::time(nullptr)) {} constexpr Time(time_t ti) noexcept : ts{ti} {} constexpr Time(const Time& other) noexcept : ts{other.ts} {} [[nodiscard]] constexpr time_t getTs() const { return ts; } constexpr Time& operator=(const Time& other) noexcept { ts = other.ts; return *this; } [[nodiscard]] constexpr bool operator==(const Time& other) const noexcept { return ts == other.ts; } [[nodiscard]] constexpr bool operator!=(const Time& other) const noexcept { return ts != other.ts; } [[nodiscard]] constexpr bool operator<(const Time& other) const noexcept { return ts < other.ts; } [[nodiscard]] constexpr bool operator>(const Time& other) const noexcept { return ts > other.ts; } [[nodiscard]] constexpr bool operator<=(const Time& other) const noexcept { return ts <= other.ts; } [[nodiscard]] constexpr bool operator>=(const Time& other) const noexcept { return ts >= other.ts; } }; /** * @brief Case-insensitive comparator for std::map sorting */ struct CaseInsensitiveCompare { // Allow heterogenous lookup with maps that use this comparator. using is_transparent = void; bool operator()(std::string_view lhs, std::string_view rhs) const { return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), [](char lhs, char rhs) { return std::tolower(static_cast(lhs)) < std::tolower(static_cast(rhs)); }); } bool operator()(std::wstring_view lhs, std::wstring_view rhs) const { return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), [](wchar_t lhs, wchar_t rhs) { return std::towlower(lhs) < std::towlower(rhs); }); } }; /** * @brief Directory traversal tool for accessing sorted directory entries */ class DirectoryEnumerator { public: enum class Mode { Native, DirsSorted, FilesSorted, DirsThenFilesSorted }; struct Entry { hecl::SystemString m_path; hecl::SystemString m_name; size_t m_fileSz; bool m_isDir; Entry(hecl::SystemString path, const hecl::SystemChar* name, size_t sz, bool isDir) : m_path(std::move(path)), m_name(name), m_fileSz(sz), m_isDir(isDir) {} }; private: std::vector m_entries; public: DirectoryEnumerator(SystemStringView path, Mode mode = Mode::DirsThenFilesSorted, bool sizeSort = false, bool reverse = false, bool noHidden = false); explicit operator bool() const { return m_entries.size() != 0; } size_t size() const { return m_entries.size(); } std::vector::const_iterator begin() const { return m_entries.cbegin(); } std::vector::const_iterator end() const { return m_entries.cend(); } }; /** * @brief Build list of common OS-specific directories */ std::vector> GetSystemLocations(); /** * @brief Special ProjectRootPath class for opening Database::Project instances * * Constructing a ProjectPath requires supplying a ProjectRootPath to consistently * resolve canonicalized relative paths. */ class ProjectRootPath { SystemString m_projRoot; Hash m_hash = 0; public: /** * @brief Empty constructor * * Used to preallocate ProjectPath for later population using assign() */ ProjectRootPath() = default; /** * @brief Tests for non-empty project root path */ explicit operator bool() const { return m_projRoot.size() != 0; } /** * @brief Construct a representation of a project root path * @param path valid filesystem-path (relative or absolute) to project root */ ProjectRootPath(SystemStringView path) : m_projRoot(path) { SanitizePath(m_projRoot); m_hash = Hash(m_projRoot); } /** * @brief Access fully-canonicalized absolute path * @return Absolute path reference */ SystemStringView getAbsolutePath() const { return m_projRoot; } /** * @brief Make absolute path project relative * @param absPath Absolute path * @return SystemString of path relative to project root */ SystemString getProjectRelativeFromAbsolute(SystemStringView absPath) const { if (absPath.size() > m_projRoot.size()) { SystemString absPathForward(absPath); for (SystemChar& ch : absPathForward) if (ch == _SYS_STR('\\')) ch = _SYS_STR('/'); if (!absPathForward.compare(0, m_projRoot.size(), m_projRoot)) { auto beginIt = absPathForward.cbegin() + m_projRoot.size(); while (*beginIt == _SYS_STR('/')) ++beginIt; return SystemString(beginIt, absPathForward.cend()); } } LogModule.report(logvisor::Fatal, FMT_STRING(_SYS_STR("unable to resolve '{}' as project relative '{}'")), absPath, m_projRoot); return SystemString(); } /** * @brief Create directory at path * * Fatal log report is issued if directory is not able to be created or doesn't already exist. * If directory already exists, no action taken. */ void makeDir() const { MakeDir(m_projRoot.c_str()); } /** * @brief HECL-specific xxhash * @return unique hash value */ Hash hash() const noexcept { return m_hash; } bool operator==(const ProjectRootPath& other) const noexcept { return m_hash == other.m_hash; } bool operator!=(const ProjectRootPath& other) const noexcept { return !operator==(other); } /** * @brief Obtain c-string of final path component * @return Final component c-string (may be empty) */ SystemStringView getLastComponent() const { size_t pos = m_projRoot.rfind(_SYS_STR('/')); if (pos == SystemString::npos) return {}; return {m_projRoot.c_str() + pos + 1, size_t(m_projRoot.size() - pos - 1)}; } }; /** * @brief Canonicalized project path representation using POSIX conventions * * HECL uses POSIX-style paths (with '/' separator) and directory tokens * ('.','..') to resolve files within a project. The database internally * uses this representation to track working files. * * This class provides a convenient way to resolve paths relative to the * project root. Part of this representation involves resolving symbolic * links to regular file/directory paths and determining its type. * * NOTE THAT PROJECT PATHS ARE TREATED AS CASE SENSITIVE!! */ class ProjectPath { Database::Project* m_proj = nullptr; SystemString m_absPath; SystemString m_relPath; SystemString m_auxInfo; Hash m_hash = 0; #if HECL_UCS2 std::string m_utf8AbsPath; std::string m_utf8RelPath; std::string m_utf8AuxInfo; #endif void ComputeHash() { #if HECL_UCS2 m_utf8AbsPath = WideToUTF8(m_absPath); m_utf8RelPath = WideToUTF8(m_relPath); m_utf8AuxInfo = WideToUTF8(m_auxInfo); if (m_utf8AuxInfo.size()) m_hash = Hash(m_utf8RelPath + '|' + m_utf8AuxInfo); else m_hash = Hash(m_utf8RelPath); #else if (m_auxInfo.size()) m_hash = Hash(m_relPath + '|' + m_auxInfo); else m_hash = Hash(m_relPath); #endif } public: /** * @brief Empty constructor * * Used to preallocate ProjectPath for later population using assign() */ ProjectPath() = default; /** * @brief Tests for non-empty project path */ explicit operator bool() const { return m_absPath.size() != 0; } /** * @brief Clears path */ void clear() { m_proj = nullptr; m_absPath.clear(); m_relPath.clear(); m_hash = 0; #if HECL_UCS2 m_utf8AbsPath.clear(); m_utf8RelPath.clear(); #endif } /** * @brief Construct a project subpath representation within a project's root path * @param project previously constructed Project to use root path of * @param path valid filesystem-path (relative or absolute) to subpath */ ProjectPath(Database::Project& project, SystemStringView path) { assign(project, path); } void assign(Database::Project& project, SystemStringView path); #if HECL_UCS2 ProjectPath(Database::Project& project, std::string_view path) { assign(project, path); } void assign(Database::Project& project, std::string_view path); #endif /** * @brief Construct a project subpath representation within another subpath * @param parentPath previously constructed ProjectPath which ultimately connects to a ProjectRootPath * @param path valid filesystem-path (relative or absolute) to subpath */ ProjectPath(const ProjectPath& parentPath, SystemStringView path) { assign(parentPath, path); } void assign(const ProjectPath& parentPath, SystemStringView path); #if HECL_UCS2 ProjectPath(const ProjectPath& parentPath, std::string_view path) { assign(parentPath, path); } void assign(const ProjectPath& parentPath, std::string_view path); #endif /** * @brief Determine if ProjectPath represents project root directory * @return true if project root directory */ bool isRoot() const { return m_relPath.empty(); } /** * @brief Return new ProjectPath with extension added * @param ext file extension to add (nullptr may be passed to remove the extension) * @param replace remove existing extension (if any) before appending new extension * @return new path with extension */ ProjectPath getWithExtension(const SystemChar* ext, bool replace = false) const; /** * @brief Access fully-canonicalized absolute path * @return Absolute path reference */ SystemStringView getAbsolutePath() const { return m_absPath; } /** * @brief Access fully-canonicalized project-relative path * @return Relative pointer to within absolute-path or "." for project root-directory (use isRoot to detect) */ SystemStringView getRelativePath() const { if (m_relPath.size()) return m_relPath; static const SystemString dot = _SYS_STR("."); return dot; } /** * @brief Obtain cooked equivalent of this ProjectPath * @param spec DataSpec to get path against * @return Cooked representation path */ ProjectPath getCookedPath(const Database::DataSpecEntry& spec) const; /** * @brief Obtain path of parent entity (a directory for file paths) * @return Parent Path * * This will not resolve outside the project root (error in that case) */ ProjectPath getParentPath() const { if (m_relPath == _SYS_STR(".")) LogModule.report(logvisor::Fatal, FMT_STRING("attempted to resolve parent of root project path")); size_t pos = m_relPath.rfind(_SYS_STR('/')); if (pos == SystemString::npos) return ProjectPath(*m_proj, _SYS_STR("")); return ProjectPath(*m_proj, SystemString(m_relPath.begin(), m_relPath.begin() + pos)); } /** * @brief Obtain c-string of final path component (stored within relative path) * @return Final component c-string (may be empty) */ SystemStringView getLastComponent() const { size_t pos = m_relPath.rfind(_SYS_STR('/')); if (pos == SystemString::npos) return m_relPath; return {m_relPath.c_str() + pos + 1, m_relPath.size() - pos - 1}; } std::string_view getLastComponentUTF8() const { size_t pos = m_relPath.rfind(_SYS_STR('/')); #if HECL_UCS2 if (pos == SystemString::npos) return m_utf8RelPath; return {m_utf8RelPath.c_str() + pos + 1, size_t(m_utf8RelPath.size() - pos - 1)}; #else if (pos == SystemString::npos) return m_relPath; return {m_relPath.c_str() + pos + 1, size_t(m_relPath.size() - pos - 1)}; #endif } /** * @brief Obtain c-string of extension of final path component (stored within relative path) * @return Final component extension c-string (may be empty) */ SystemStringView getLastComponentExt() const { SystemStringView lastCompOrig = getLastComponent().data(); const SystemChar* end = lastCompOrig.data() + lastCompOrig.size(); const SystemChar* lastComp = end; while (lastComp != lastCompOrig.data()) { if (*lastComp == _SYS_STR('.')) return {lastComp + 1, size_t(end - lastComp - 1)}; --lastComp; } return {}; } /** * @brief Build vector of project-relative directory/file components * @return Vector of path components */ std::vector getPathComponents() const { std::vector ret; if (m_relPath.empty()) return ret; auto it = m_relPath.cbegin(); if (*it == _SYS_STR('/')) { ret.push_back(_SYS_STR("/")); ++it; } hecl::SystemString comp; for (; it != m_relPath.cend(); ++it) { if (*it == _SYS_STR('/')) { if (comp.empty()) continue; ret.push_back(std::move(comp)); comp.clear(); continue; } comp += *it; } if (comp.size()) ret.push_back(std::move(comp)); return ret; } /** * @brief Build vector of project-relative directory/file components * @return Vector of path components encoded as UTF8 */ std::vector getPathComponentsUTF8() const { #if HECL_UCS2 const std::string& relPath = m_utf8RelPath; #else const std::string& relPath = m_relPath; #endif std::vector ret; if (relPath.empty()) return ret; auto it = relPath.cbegin(); if (*it == '/') { ret.push_back("/"); ++it; } std::string comp; for (; it != relPath.cend(); ++it) { if (*it == '/') { if (comp.empty()) continue; ret.push_back(std::move(comp)); comp.clear(); continue; } comp += *it; } if (comp.size()) ret.push_back(std::move(comp)); return ret; } /** * @brief Access fully-canonicalized absolute path in UTF-8 * @return Absolute path reference */ std::string_view getAbsolutePathUTF8() const { #if HECL_UCS2 return m_utf8AbsPath; #else return m_absPath; #endif } std::string_view getRelativePathUTF8() const { #if HECL_UCS2 return m_utf8RelPath; #else return m_relPath; #endif } SystemStringView getAuxInfo() const { return m_auxInfo; } std::string_view getAuxInfoUTF8() const { #if HECL_UCS2 return m_utf8AuxInfo; #else return m_auxInfo; #endif } /** * @brief Construct a path with the aux info overwritten with specified string * @param auxStr string to replace existing auxInfo with */ ProjectPath ensureAuxInfo(SystemStringView auxStr) const { if (auxStr.empty()) return ProjectPath(getProject(), getRelativePath()); else return ProjectPath(getProject(), SystemString(getRelativePath()) + _SYS_STR('|') + auxStr.data()); } #if HECL_UCS2 ProjectPath ensureAuxInfo(std::string_view auxStr) const { return ProjectPath(getProject(), SystemString(getRelativePath()) + _SYS_STR('|') + UTF8ToWide(auxStr)); } #endif template class EncodableString { friend class ProjectPath; using EncStringView = std::basic_string_view; StringT m_ownedString; EncStringView m_stringView; EncodableString(StringT s) : m_ownedString(std::move(s)), m_stringView(m_ownedString) {} EncodableString(EncStringView sv) : m_stringView(sv) {} EncodableString(const EncodableString&) = delete; EncodableString& operator=(const EncodableString&) = delete; EncodableString(EncodableString&&) = delete; EncodableString& operator=(EncodableString&&) = delete; public: operator EncStringView() const { return m_stringView; } }; EncodableString getEncodableString() const { if (!getAuxInfo().empty()) return {SystemString(getRelativePath()) + _SYS_STR('|') + getAuxInfo().data()}; else return {getRelativePath()}; } EncodableString getEncodableStringUTF8() const { if (!getAuxInfo().empty()) return {std::string(getRelativePathUTF8()) + '|' + getAuxInfoUTF8().data()}; else return {getRelativePathUTF8()}; } /** * @brief Type of path */ enum class Type { None, /**< If path doesn't reference a valid filesystem entity, this is returned */ File, /**< Singular file path (confirmed with filesystem) */ Directory, /**< Singular directory path (confirmed with filesystem) */ Glob, /**< Glob-path (whenever one or more '*' occurs in syntax) */ }; /** * @brief Get type of path based on syntax and filesystem queries * @return Type of path */ Type getPathType() const; /** * @brief Test if nothing exists at path * @return True if nothing exists at path */ bool isNone() const { return getPathType() == Type::None; } /** * @brief Test if regular file exists at path * @return True if regular file exists at path */ bool isFile() const { return getPathType() == Type::File; } /** * @brief Test if directory exists at path * @return True if directory exists at path */ bool isDirectory() const { return getPathType() == Type::Directory; } /** * @brief Certain singular resource targets are cooked based on this test * @return True if file or glob */ bool isFileOrGlob() const { Type type = getPathType(); return (type == Type::File || type == Type::Glob); } /** * @brief Get time of last modification with special behaviors for directories and glob-paths * @return Time object representing entity's time of last modification * * Regular files simply return their modtime as queried from the OS * Directories return the latest modtime of all first-level regular files * Glob-paths return the latest modtime of all matched regular files */ Time getModtime() const; /** * @brief Insert directory children into list * @param outPaths list to append children to */ void getDirChildren(std::map& outPaths) const; /** * @brief Construct DirectoryEnumerator set to project path */ hecl::DirectoryEnumerator enumerateDir() const; /** * @brief Insert glob matches into existing vector * @param outPaths Vector to add matches to (will not erase existing contents) */ void getGlobResults(std::vector& outPaths) const; /** * @brief Count how many directory levels deep in project path is * @return Level Count */ size_t levelCount() const { size_t count = 0; for (SystemChar ch : m_relPath) if (ch == _SYS_STR('/') || ch == _SYS_STR('\\')) ++count; return count; } /** * @brief Create directory at path * * Fatal log report is issued if directory is not able to be created or doesn't already exist. * If directory already exists, no action taken. */ void makeDir() const { MakeDir(m_absPath.c_str()); } /** * @brief Create directory chain leading up to path * @param includeLastComp if set, the ProjectPath is assumed to be a * directory, creating the final component */ void makeDirChain(bool includeLastComp) const { std::vector comps = getPathComponents(); auto end = comps.cend(); if (end != comps.cbegin() && !includeLastComp) --end; ProjectPath compPath(*m_proj, _SYS_STR(".")); for (auto it = comps.cbegin(); it != end; ++it) { compPath = ProjectPath(compPath, *it); compPath.makeDir(); } } /** * @brief Fetch project that contains path * @return Project */ Database::Project& getProject() const { if (!m_proj) LogModule.report(logvisor::Fatal, FMT_STRING("ProjectPath::getProject() called on unqualified path")); return *m_proj; } /** * @brief HECL-specific xxhash * @return unique hash value */ Hash hash() const noexcept { return m_hash; } bool operator==(const ProjectPath& other) const noexcept { return m_hash == other.m_hash; } bool operator!=(const ProjectPath& other) const noexcept { return !operator==(other); } uint32_t parsedHash32() const; }; /** * @brief Handy functions not directly provided via STL strings */ class StringUtils { public: static bool BeginsWith(SystemStringView str, SystemStringView test) { if (test.size() > str.size()) return false; return str.compare(0, test.size(), test) == 0; } static bool EndsWith(SystemStringView str, SystemStringView test) { if (test.size() > str.size()) return false; return str.compare(str.size() - test.size(), SystemStringView::npos, test) == 0; } static std::string TrimWhitespace(std::string_view str) { auto bit = str.begin(); while (bit != str.cend() && std::isspace(static_cast(*bit))) ++bit; auto eit = str.end(); while (eit != str.cbegin() && std::isspace(static_cast(*(eit - 1)))) --eit; return {bit, eit}; } #if HECL_UCS2 static bool BeginsWith(std::string_view str, std::string_view test) { if (test.size() > str.size()) return false; return str.compare(0, test.size(), test) == 0; } static bool EndsWith(std::string_view str, std::string_view test) { if (test.size() > str.size()) return false; return str.compare(str.size() - test.size(), std::string_view::npos, test) == 0; } static SystemString TrimWhitespace(SystemStringView str) { auto bit = str.begin(); while (bit != str.cend() && std::iswspace(*bit)) ++bit; auto eit = str.end(); while (eit != str.cbegin() && std::iswspace(*(eit - 1))) --eit; return {bit, eit}; } #endif }; /** * @brief Mutex-style centralized resource-path tracking * * Provides a means to safely parallelize resource processing; detecting when another * thread is working on the same resource. */ class ResourceLock { static bool SetThreadRes(const ProjectPath& path); static void ClearThreadRes(); bool good; public: explicit operator bool() const { return good; } static bool InProgress(const ProjectPath& path); explicit ResourceLock(const ProjectPath& path) : good{SetThreadRes(path)} {} ~ResourceLock() { if (good) ClearThreadRes(); } ResourceLock(const ResourceLock&) = delete; ResourceLock& operator=(const ResourceLock&) = delete; ResourceLock(ResourceLock&&) = delete; ResourceLock& operator=(ResourceLock&&) = delete; }; /** * @brief Search from within provided directory for the project root * @param path absolute or relative file path to search from * @return Newly-constructed root path (bool-evaluating to false if not found) */ ProjectRootPath SearchForProject(SystemStringView path); /** * @brief Search from within provided directory for the project root * @param path absolute or relative file path to search from * @param subpathOut remainder of provided path assigned to this ProjectPath * @return Newly-constructed root path (bool-evaluating to false if not found) */ ProjectRootPath SearchForProject(SystemStringView path, SystemString& subpathOut); /** * @brief Test if given path is a PNG (based on file header) * @param path Path to test * @return true if PNG */ bool IsPathPNG(const hecl::ProjectPath& path); /** * @brief Test if given path is a blend (based on file header) * @param path Path to test * @return true if blend */ bool IsPathBlend(const hecl::ProjectPath& path); /** * @brief Test if given path is a yaml (based on file extension) * @param path Path to test * @return true if yaml */ bool IsPathYAML(const hecl::ProjectPath& path); #undef bswap16 #undef bswap32 #undef bswap64 /* Type-sensitive byte swappers */ template constexpr T bswap16(T val) noexcept { #if __GNUC__ return __builtin_bswap16(val); #elif _WIN32 return _byteswap_ushort(val); #else return (val = (val << 8) | ((val >> 8) & 0xFF)); #endif } template constexpr T bswap32(T val) noexcept { #if __GNUC__ return __builtin_bswap32(val); #elif _WIN32 return _byteswap_ulong(val); #else val = (val & 0x0000FFFF) << 16 | (val & 0xFFFF0000) >> 16; val = (val & 0x00FF00FF) << 8 | (val & 0xFF00FF00) >> 8; return val; #endif } template constexpr T bswap64(T val) noexcept { #if __GNUC__ return __builtin_bswap64(val); #elif _WIN32 return _byteswap_uint64(val); #else return ((val & 0xFF00000000000000ULL) >> 56) | ((val & 0x00FF000000000000ULL) >> 40) | ((val & 0x0000FF0000000000ULL) >> 24) | ((val & 0x000000FF00000000ULL) >> 8) | ((val & 0x00000000FF000000ULL) << 8) | ((val & 0x0000000000FF0000ULL) << 24) | ((val & 0x000000000000FF00ULL) << 40) | ((val & 0x00000000000000FFULL) << 56); #endif } #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ constexpr int16_t SBig(int16_t val) noexcept { return bswap16(val); } constexpr uint16_t SBig(uint16_t val) noexcept { return bswap16(val); } constexpr int32_t SBig(int32_t val) noexcept { return bswap32(val); } constexpr uint32_t SBig(uint32_t val) noexcept { return bswap32(val); } constexpr int64_t SBig(int64_t val) noexcept { return bswap64(val); } constexpr uint64_t SBig(uint64_t val) noexcept { return bswap64(val); } constexpr float SBig(float val) noexcept { union { float f; atInt32 i; } uval1 = {val}; union { atInt32 i; float f; } uval2 = {bswap32(uval1.i)}; return uval2.f; } constexpr double SBig(double val) noexcept { union { double f; atInt64 i; } uval1 = {val}; union { atInt64 i; double f; } uval2 = {bswap64(uval1.i)}; return uval2.f; } #ifndef SBIG #define SBIG(q) (((q)&0x000000FF) << 24 | ((q)&0x0000FF00) << 8 | ((q)&0x00FF0000) >> 8 | ((q)&0xFF000000) >> 24) #endif constexpr int16_t SLittle(int16_t val) noexcept { return val; } constexpr uint16_t SLittle(uint16_t val) noexcept { return val; } constexpr int32_t SLittle(int32_t val) noexcept { return val; } constexpr uint32_t SLittle(uint32_t val) noexcept { return val; } constexpr int64_t SLittle(int64_t val) noexcept { return val; } constexpr uint64_t SLittle(uint64_t val) noexcept { return val; } constexpr float SLittle(float val) noexcept { return val; } constexpr double SLittle(double val) noexcept { return val; } #ifndef SLITTLE #define SLITTLE(q) (q) #endif #else constexpr int16_t SLittle(int16_t val) noexcept { return bswap16(val); } constexpr uint16_t SLittle(uint16_t val) noexcept { return bswap16(val); } constexpr int32_t SLittle(int32_t val) noexcept { return bswap32(val); } constexpr uint32_t SLittle(uint32_t val) noexcept { return bswap32(val); } constexpr int64_t SLittle(int64_t val) noexcept { return bswap64(val); } constexpr uint64_t SLittle(uint64_t val) noexcept { return bswap64(val); } constexpr float SLittle(float val) noexcept { int32_t ival = bswap32(*((int32_t*)(&val))); return *((float*)(&ival)); } constexpr double SLittle(double val) noexcept { int64_t ival = bswap64(*((int64_t*)(&val))); return *((double*)(&ival)); } #ifndef SLITTLE #define SLITTLE(q) (((q)&0x000000FF) << 24 | ((q)&0x0000FF00) << 8 | ((q)&0x00FF0000) >> 8 | ((q)&0xFF000000) >> 24) #endif constexpr int16_t SBig(int16_t val) noexcept { return val; } constexpr uint16_t SBig(uint16_t val) noexcept { return val; } constexpr int32_t SBig(int32_t val) noexcept { return val; } constexpr uint32_t SBig(uint32_t val) noexcept { return val; } constexpr int64_t SBig(int64_t val) noexcept { return val; } constexpr uint64_t SBig(uint64_t val) noexcept { return val; } constexpr float SBig(float val) noexcept { return val; } constexpr double SBig(double val) noexcept { return val; } #ifndef SBIG #define SBIG(q) (q) #endif #endif template constexpr void hash_combine_impl(SizeT& seed, SizeT value) noexcept { seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2); } } // namespace hecl #define CHAINED_SIGNAL_HANDLER(name, signal) \ class ChainedSignalHandler_##name { \ typedef void(*sighandler_t)(int); \ typedef void(*sigaction_t)(int, siginfo_t*, void*); \ static std::atomic_bool m_isSetup; \ static sighandler_t m_nextsh; \ static sigaction_t m_nextsa; \ static void my_sig_action(int sig, siginfo_t* si, void* ctx); \ static void sig_action(int sig, siginfo_t* si, void* ctx) { \ my_sig_action(sig, si, ctx); \ if (m_nextsa) \ m_nextsa(sig, si, ctx); \ else if (m_nextsh) \ m_nextsh(sig); \ } \ public: \ static void setup() { \ if (ChainedSignalHandler_##name::m_isSetup.exchange(true) == true) \ return; \ { \ struct sigaction sold; \ if (sigaction(signal, nullptr, &sold) == 0) { \ if (sold.sa_flags & SA_SIGINFO) \ m_nextsa = sold.sa_sigaction; \ else \ m_nextsh = sold.sa_handler; \ } \ } \ { \ struct sigaction snew = {}; \ snew.sa_sigaction = sig_action; \ snew.sa_flags = SA_RESTART | SA_NOCLDSTOP | SA_SIGINFO; \ sigaction(signal, &snew, nullptr); \ } \ } \ }; \ std::atomic_bool ChainedSignalHandler_##name::m_isSetup = {false}; \ ChainedSignalHandler_##name::sighandler_t ChainedSignalHandler_##name::m_nextsh = {}; \ ChainedSignalHandler_##name::sigaction_t ChainedSignalHandler_##name::m_nextsa = {}; \ inline void ChainedSignalHandler_##name::my_sig_action #define SETUP_CHAINED_SIGNAL_HANDLER(name) \ ChainedSignalHandler_##name::setup() namespace std { template <> struct hash { size_t operator()(const hecl::ProjectPath& val) const noexcept { return val.hash().valSizeT(); } }; template <> struct hash { size_t operator()(const hecl::Hash& val) const noexcept { return val.valSizeT(); } }; } // namespace std FMT_CUSTOM_FORMATTER(hecl::SystemUTF8Conv, "{}", obj.str()) FMT_CUSTOM_FORMATTER(hecl::SystemStringConv, "{}", obj.sys_str())