diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/common/mptPathString.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/common/mptPathString.cpp')
-rw-r--r-- | Src/external_dependencies/openmpt-trunk/common/mptPathString.cpp | 872 |
1 files changed, 872 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/common/mptPathString.cpp b/Src/external_dependencies/openmpt-trunk/common/mptPathString.cpp new file mode 100644 index 00000000..d41a5494 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/common/mptPathString.cpp @@ -0,0 +1,872 @@ +/* + * mptPathString.cpp + * ----------------- + * Purpose: Wrapper class around the platform-native representation of path names. Should be the only type that is used to store path names. + * Notes : Currently none. + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#include "stdafx.h" +#include "mptPathString.h" + +#include "mpt/uuid/uuid.hpp" + +#include "misc_util.h" + +#include "mptRandom.h" + +#if MPT_OS_WINDOWS +#include <windows.h> +#if defined(MODPLUG_TRACKER) +#include <shlwapi.h> +#endif +#include <tchar.h> +#endif + +OPENMPT_NAMESPACE_BEGIN + +#if MPT_OS_WINDOWS + +namespace mpt +{ + + +RawPathString PathString::AsNativePrefixed() const +{ +#if MPT_OS_WINDOWS_WINRT && (_WIN32_WINNT < 0x0a00) + // For WinRT on Windows 8, there is no official wy to determine an absolute path. + return path; +#else + if(path.length() < MAX_PATH || path.substr(0, 4) == PL_("\\\\?\\")) + { + // Path is short enough or already in prefixed form + return path; + } + const RawPathString absPath = mpt::GetAbsolutePath(*this).AsNative(); + if(absPath.substr(0, 2) == PL_("\\\\")) + { + // Path is a network share: \\server\foo.bar -> \\?\UNC\server\foo.bar + return PL_("\\\\?\\UNC") + absPath.substr(1); + } else + { + // Regular file: C:\foo.bar -> \\?\C:\foo.bar + return PL_("\\\\?\\") + absPath; + } +#endif +} + + +#if !MPT_OS_WINDOWS_WINRT + +int PathString::CompareNoCase(const PathString & a, const PathString & b) +{ + return lstrcmpi(a.path.c_str(), b.path.c_str()); +} + +#endif // !MPT_OS_WINDOWS_WINRT + + +// Convert a path to its simplified form, i.e. remove ".\" and "..\" entries +// Note: We use our own implementation as PathCanonicalize is limited to MAX_PATH +// and unlimited versions are only available on Windows 8 and later. +// Furthermore, we also convert forward-slashes to backslashes and always remove trailing slashes. +PathString PathString::Simplify() const +{ + if(path.empty()) + return PathString(); + + std::vector<RawPathString> components; + RawPathString root; + RawPathString::size_type startPos = 0; + if(path.size() >= 2 && path[1] == PC_(':')) + { + // Drive letter + root = path.substr(0, 2) + PC_('\\'); + startPos = 2; + } else if(path.substr(0, 2) == PL_("\\\\")) + { + // Network share + root = PL_("\\\\"); + startPos = 2; + } else if(path.substr(0, 2) == PL_(".\\") || path.substr(0, 2) == PL_("./")) + { + // Special case for relative paths + root = PL_(".\\"); + startPos = 2; + } else if(path.size() >= 1 && (path[0] == PC_('\\') || path[0] == PC_('/'))) + { + // Special case for relative paths + root = PL_("\\"); + startPos = 1; + } + + while(startPos < path.size()) + { + auto pos = path.find_first_of(PL_("\\/"), startPos); + if(pos == RawPathString::npos) + pos = path.size(); + mpt::RawPathString dir = path.substr(startPos, pos - startPos); + if(dir == PL_("..")) + { + // Go back one directory + if(!components.empty()) + { + components.pop_back(); + } + } else if(dir == PL_(".")) + { + // nop + } else if(!dir.empty()) + { + components.push_back(std::move(dir)); + } + startPos = pos + 1; + } + + RawPathString result = root; + result.reserve(path.size()); + for(const auto &component : components) + { + result += component + PL_("\\"); + } + if(!components.empty()) + result.pop_back(); + return mpt::PathString(result); +} + +} // namespace mpt + +#endif // MPT_OS_WINDOWS + + +namespace mpt +{ + + +#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS + +void PathString::SplitPath(PathString *drive, PathString *dir, PathString *fname, PathString *ext) const +{ + // We cannot use CRT splitpath here, because: + // * limited to _MAX_PATH or similar + // * no support for UNC paths + // * no support for \\?\ prefixed paths + + if(drive) *drive = mpt::PathString(); + if(dir) *dir = mpt::PathString(); + if(fname) *fname = mpt::PathString(); + if(ext) *ext = mpt::PathString(); + + mpt::RawPathString p = path; + + // remove \\?\\ prefix + if(p.substr(0, 8) == PL_("\\\\?\\UNC\\")) + { + p = PL_("\\\\") + p.substr(8); + } else if(p.substr(0, 4) == PL_("\\\\?\\")) + { + p = p.substr(4); + } + + if (p.length() >= 2 && ( + p.substr(0, 2) == PL_("\\\\") + || p.substr(0, 2) == PL_("\\/") + || p.substr(0, 2) == PL_("/\\") + || p.substr(0, 2) == PL_("//") + )) + { // UNC + mpt::RawPathString::size_type first_slash = p.substr(2).find_first_of(PL_("\\/")); + if(first_slash != mpt::RawPathString::npos) + { + mpt::RawPathString::size_type second_slash = p.substr(2 + first_slash + 1).find_first_of(PL_("\\/")); + if(second_slash != mpt::RawPathString::npos) + { + if(drive) *drive = mpt::PathString::FromNative(p.substr(0, 2 + first_slash + 1 + second_slash)); + p = p.substr(2 + first_slash + 1 + second_slash); + } else + { + if(drive) *drive = mpt::PathString::FromNative(p); + p = mpt::RawPathString(); + } + } else + { + if(drive) *drive = mpt::PathString::FromNative(p); + p = mpt::RawPathString(); + } + } else + { // local + if(p.length() >= 2 && (p[1] == PC_(':'))) + { + if(drive) *drive = mpt::PathString::FromNative(p.substr(0, 2)); + p = p.substr(2); + } else + { + if(drive) *drive = mpt::PathString(); + } + } + mpt::RawPathString::size_type last_slash = p.find_last_of(PL_("\\/")); + if(last_slash != mpt::RawPathString::npos) + { + if(dir) *dir = mpt::PathString::FromNative(p.substr(0, last_slash + 1)); + p = p.substr(last_slash + 1); + } else + { + if(dir) *dir = mpt::PathString(); + } + mpt::RawPathString::size_type last_dot = p.find_last_of(PL_(".")); + if(last_dot == mpt::RawPathString::npos) + { + if(fname) *fname = mpt::PathString::FromNative(p); + if(ext) *ext = mpt::PathString(); + } else if(last_dot == 0) + { + if(fname) *fname = mpt::PathString::FromNative(p); + if(ext) *ext = mpt::PathString(); + } else if(p == PL_(".") || p == PL_("..")) + { + if(fname) *fname = mpt::PathString::FromNative(p); + if(ext) *ext = mpt::PathString(); + } else + { + if(fname) *fname = mpt::PathString::FromNative(p.substr(0, last_dot)); + if(ext) *ext = mpt::PathString::FromNative(p.substr(last_dot)); + } + +} + +PathString PathString::GetDrive() const +{ + PathString drive; + SplitPath(&drive, nullptr, nullptr, nullptr); + return drive; +} +PathString PathString::GetDir() const +{ + PathString dir; + SplitPath(nullptr, &dir, nullptr, nullptr); + return dir; +} +PathString PathString::GetPath() const +{ + PathString drive, dir; + SplitPath(&drive, &dir, nullptr, nullptr); + return drive + dir; +} +PathString PathString::GetFileName() const +{ + PathString fname; + SplitPath(nullptr, nullptr, &fname, nullptr); + return fname; +} +PathString PathString::GetFileExt() const +{ + PathString ext; + SplitPath(nullptr, nullptr, nullptr, &ext); + return ext; +} +PathString PathString::GetFullFileName() const +{ + PathString name, ext; + SplitPath(nullptr, nullptr, &name, &ext); + return name + ext; +} + + +bool PathString::IsDirectory() const +{ + // Using PathIsDirectoryW here instead would increase libopenmpt dependencies by shlwapi.dll. + // GetFileAttributesW also does the job just fine. + #if MPT_OS_WINDOWS_WINRT + WIN32_FILE_ATTRIBUTE_DATA data = {}; + if(::GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &data) == 0) + { + return false; + } + DWORD dwAttrib = data.dwFileAttributes; + #else // !MPT_OS_WINDOWS_WINRT + DWORD dwAttrib = ::GetFileAttributes(path.c_str()); + #endif // MPT_OS_WINDOWS_WINRT + return ((dwAttrib != INVALID_FILE_ATTRIBUTES) && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); +} + +bool PathString::IsFile() const +{ + #if MPT_OS_WINDOWS_WINRT + WIN32_FILE_ATTRIBUTE_DATA data = {}; + if (::GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &data) == 0) + { + return false; + } + DWORD dwAttrib = data.dwFileAttributes; + #else // !MPT_OS_WINDOWS_WINRT + DWORD dwAttrib = ::GetFileAttributes(path.c_str()); + #endif // MPT_OS_WINDOWS_WINRT + return ((dwAttrib != INVALID_FILE_ATTRIBUTES) && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); +} + +#endif // MODPLUG_TRACKER && MPT_OS_WINDOWS + + +#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS + +bool PathString::FileOrDirectoryExists() const +{ + return ::PathFileExists(path.c_str()) != FALSE; +} + +#endif // MODPLUG_TRACKER && MPT_OS_WINDOWS + + +#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS + +PathString PathString::ReplaceExt(const mpt::PathString &newExt) const +{ + return GetDrive() + GetDir() + GetFileName() + newExt; +} + + +PathString PathString::SanitizeComponent() const +{ + PathString result = *this; + SanitizeFilename(result); + return result; +} + + +// Convert an absolute path to a path that's relative to "&relativeTo". +PathString PathString::AbsolutePathToRelative(const PathString &relativeTo) const +{ + mpt::PathString result = *this; + if(path.empty()) + { + return result; + } + if(!_tcsncicmp(relativeTo.AsNative().c_str(), AsNative().c_str(), relativeTo.AsNative().length())) + { + // Path is OpenMPT's directory or a sub directory ("C:\OpenMPT\Somepath" => ".\Somepath") + result = P_(".\\"); // ".\" + result += mpt::PathString::FromNative(AsNative().substr(relativeTo.AsNative().length())); + } else if(!_tcsncicmp(relativeTo.AsNative().c_str(), AsNative().c_str(), 2)) + { + // Path is on the same drive as OpenMPT ("C:\Somepath" => "\Somepath") + result = mpt::PathString::FromNative(AsNative().substr(2)); + } + return result; +} + + +// Convert a path that is relative to "&relativeTo" to an absolute path. +PathString PathString::RelativePathToAbsolute(const PathString &relativeTo) const +{ + mpt::PathString result = *this; + if(path.empty()) + { + return result; + } + if(path.length() >= 2 && path[0] == PC_('\\') && path[1] != PC_('\\')) + { + // Path is on the same drive as OpenMPT ("\Somepath\" => "C:\Somepath\"), but ignore network paths starting with "\\" + result = mpt::PathString::FromNative(relativeTo.AsNative().substr(0, 2)); + result += mpt::PathString(path); + } else if(path.length() >= 2 && path.substr(0, 2) == PL_(".\\")) + { + // Path is OpenMPT's directory or a sub directory (".\Somepath\" => "C:\OpenMPT\Somepath\") + result = relativeTo; // "C:\OpenMPT\" + result += mpt::PathString::FromNative(AsNative().substr(2)); + } + return result; +} + + +#endif // MODPLUG_TRACKER && MPT_OS_WINDOWS + + +bool PathString::IsPathSeparator(RawPathString::value_type c) +{ +#if MPT_OS_WINDOWS + return (c == PC_('\\')) || (c == PC_('/')); +#else + return c == PC_('/'); +#endif +} + +RawPathString::value_type PathString::GetDefaultPathSeparator() +{ +#if MPT_OS_WINDOWS + return PC_('\\'); +#else + return PC_('/'); +#endif +} + + +} // namespace mpt + + +namespace mpt +{ + +bool PathIsAbsolute(const mpt::PathString &path) { + mpt::RawPathString rawpath = path.AsNative(); +#if MPT_OS_WINDOWS + if(rawpath.substr(0, 8) == PL_("\\\\?\\UNC\\")) + { + return true; + } + if(rawpath.substr(0, 4) == PL_("\\\\?\\")) + { + return true; + } + if(rawpath.substr(0, 2) == PL_("\\\\")) + { + return true; // UNC + } + if(rawpath.substr(0, 2) == PL_("//")) + { + return true; // UNC + } + return (rawpath.length()) >= 3 && (rawpath[1] == ':') && mpt::PathString::IsPathSeparator(rawpath[2]); +#else + return (rawpath.length() >= 1) && mpt::PathString::IsPathSeparator(rawpath[0]); +#endif +} + + +#if MPT_OS_WINDOWS + +#if !(MPT_OS_WINDOWS_WINRT && (_WIN32_WINNT < 0x0a00)) + +mpt::PathString GetAbsolutePath(const mpt::PathString &path) +{ + DWORD size = GetFullPathName(path.AsNative().c_str(), 0, nullptr, nullptr); + if(size == 0) + { + return path; + } + std::vector<TCHAR> fullPathName(size, TEXT('\0')); + if(GetFullPathName(path.AsNative().c_str(), size, fullPathName.data(), nullptr) == 0) + { + return path; + } + return mpt::PathString::FromNative(fullPathName.data()); +} + +#endif + +#ifdef MODPLUG_TRACKER + +bool DeleteWholeDirectoryTree(mpt::PathString path) +{ + if(path.AsNative().empty()) + { + return false; + } + if(PathIsRelative(path.AsNative().c_str()) == TRUE) + { + return false; + } + if(!path.FileOrDirectoryExists()) + { + return true; + } + if(!path.IsDirectory()) + { + return false; + } + path.EnsureTrailingSlash(); + HANDLE hFind = NULL; + WIN32_FIND_DATA wfd = {}; + hFind = FindFirstFile((path + P_("*.*")).AsNative().c_str(), &wfd); + if(hFind != NULL && hFind != INVALID_HANDLE_VALUE) + { + do + { + mpt::PathString filename = mpt::PathString::FromNative(wfd.cFileName); + if(filename != P_(".") && filename != P_("..")) + { + filename = path + filename; + if(filename.IsDirectory()) + { + if(!DeleteWholeDirectoryTree(filename)) + { + return false; + } + } else if(filename.IsFile()) + { + if(DeleteFile(filename.AsNative().c_str()) == 0) + { + return false; + } + } + } + } while(FindNextFile(hFind, &wfd)); + FindClose(hFind); + } + if(RemoveDirectory(path.AsNative().c_str()) == 0) + { + return false; + } + return true; +} + +#endif // MODPLUG_TRACKER + +#endif // MPT_OS_WINDOWS + + + +#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS + +mpt::PathString GetExecutablePath() +{ + std::vector<TCHAR> exeFileName(MAX_PATH); + while(GetModuleFileName(0, exeFileName.data(), mpt::saturate_cast<DWORD>(exeFileName.size())) >= exeFileName.size()) + { + if(GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + return mpt::PathString(); + } + exeFileName.resize(exeFileName.size() * 2); + } + return mpt::GetAbsolutePath(mpt::PathString::FromNative(exeFileName.data()).GetPath()); +} + + +#if !MPT_OS_WINDOWS_WINRT + +mpt::PathString GetSystemPath() +{ + DWORD size = GetSystemDirectory(nullptr, 0); + std::vector<TCHAR> path(size + 1); + if(!GetSystemDirectory(path.data(), size + 1)) + { + return mpt::PathString(); + } + return mpt::PathString::FromNative(path.data()) + P_("\\"); +} + +#endif // !MPT_OS_WINDOWS_WINRT + +#endif // MODPLUG_TRACKER && MPT_OS_WINDOWS + + + +#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS + +mpt::PathString GetTempDirectory() +{ + DWORD size = GetTempPath(0, nullptr); + if(size) + { + std::vector<TCHAR> tempPath(size + 1); + if(GetTempPath(size + 1, tempPath.data())) + { + return mpt::PathString::FromNative(tempPath.data()); + } + } + // use exe directory as fallback + return mpt::GetExecutablePath(); +} + +mpt::PathString CreateTempFileName(const mpt::PathString &fileNamePrefix, const mpt::PathString &fileNameExtension) +{ + mpt::PathString filename = mpt::GetTempDirectory(); + filename += (!fileNamePrefix.empty() ? fileNamePrefix + P_("_") : mpt::PathString()); + filename += mpt::PathString::FromUnicode(mpt::UUID::GenerateLocalUseOnly(mpt::global_prng()).ToUString()); + filename += (!fileNameExtension.empty() ? P_(".") + fileNameExtension : mpt::PathString()); + return filename; +} + +TempFileGuard::TempFileGuard(const mpt::PathString &filename) + : filename(filename) +{ + return; +} + +mpt::PathString TempFileGuard::GetFilename() const +{ + return filename; +} + +TempFileGuard::~TempFileGuard() +{ + if(!filename.empty()) + { + DeleteFile(filename.AsNative().c_str()); + } +} + + +TempDirGuard::TempDirGuard(const mpt::PathString &dirname_) + : dirname(dirname_.WithTrailingSlash()) +{ + if(dirname.empty()) + { + return; + } + if(::CreateDirectory(dirname.AsNative().c_str(), NULL) == 0) + { // fail + dirname = mpt::PathString(); + } +} + +mpt::PathString TempDirGuard::GetDirname() const +{ + return dirname; +} + +TempDirGuard::~TempDirGuard() +{ + if(!dirname.empty()) + { + DeleteWholeDirectoryTree(dirname); + } +} + +#endif // MODPLUG_TRACKER && MPT_OS_WINDOWS + +} // namespace mpt + + + +#if defined(MODPLUG_TRACKER) + +static inline char SanitizeFilenameChar(char c) +{ + if( c == '\\' || + c == '\"' || + c == '/' || + c == ':' || + c == '?' || + c == '<' || + c == '>' || + c == '|' || + c == '*') + { + c = '_'; + } + return c; +} + +static inline wchar_t SanitizeFilenameChar(wchar_t c) +{ + if( c == L'\\' || + c == L'\"' || + c == L'/' || + c == L':' || + c == L'?' || + c == L'<' || + c == L'>' || + c == L'|' || + c == L'*') + { + c = L'_'; + } + return c; +} + +#if MPT_CXX_AT_LEAST(20) +static inline char8_t SanitizeFilenameChar(char8_t c) +{ + if( c == u8'\\' || + c == u8'\"' || + c == u8'/' || + c == u8':' || + c == u8'?' || + c == u8'<' || + c == u8'>' || + c == u8'|' || + c == u8'*') + { + c = u8'_'; + } + return c; +} +#endif + +void SanitizeFilename(mpt::PathString &filename) +{ + mpt::RawPathString tmp = filename.AsNative(); + for(auto &c : tmp) + { + c = SanitizeFilenameChar(c); + } + filename = mpt::PathString::FromNative(tmp); +} + +void SanitizeFilename(char *beg, char *end) +{ + for(char *it = beg; it != end; ++it) + { + *it = SanitizeFilenameChar(*it); + } +} + +void SanitizeFilename(wchar_t *beg, wchar_t *end) +{ + for(wchar_t *it = beg; it != end; ++it) + { + *it = SanitizeFilenameChar(*it); + } +} + +void SanitizeFilename(std::string &str) +{ + for(size_t i = 0; i < str.length(); i++) + { + str[i] = SanitizeFilenameChar(str[i]); + } +} + +void SanitizeFilename(std::wstring &str) +{ + for(size_t i = 0; i < str.length(); i++) + { + str[i] = SanitizeFilenameChar(str[i]); + } +} + +#if MPT_USTRING_MODE_UTF8 +void SanitizeFilename(mpt::u8string &str) +{ + for(size_t i = 0; i < str.length(); i++) + { + str[i] = SanitizeFilenameChar(str[i]); + } +} +#endif // MPT_USTRING_MODE_UTF8 + +#if defined(MPT_WITH_MFC) +void SanitizeFilename(CString &str) +{ + for(int i = 0; i < str.GetLength(); i++) + { + str.SetAt(i, SanitizeFilenameChar(str.GetAt(i))); + } +} +#endif // MPT_WITH_MFC + +#endif // MODPLUG_TRACKER + + +#if defined(MODPLUG_TRACKER) + + +mpt::PathString FileType::AsFilterString(FlagSet<FileTypeFormat> format) const +{ + mpt::PathString filter; + if(GetShortName().empty() || GetExtensions().empty()) + { + return filter; + } + if(!GetDescription().empty()) + { + filter += mpt::PathString::FromUnicode(GetDescription()); + } else + { + filter += mpt::PathString::FromUnicode(GetShortName()); + } + const auto extensions = GetExtensions(); + if(format[FileTypeFormatShowExtensions]) + { + filter += P_(" ("); + bool first = true; + for(const auto &ext : extensions) + { + if(first) + { + first = false; + } else + { + filter += P_(","); + } + filter += P_("*."); + filter += ext; + } + filter += P_(")"); + } + filter += P_("|"); + { + bool first = true; + for(const auto &ext : extensions) + { + if(first) + { + first = false; + } else + { + filter += P_(";"); + } + filter += P_("*."); + filter += ext; + } + } + filter += P_("|"); + return filter; +} + + +mpt::PathString FileType::AsFilterOnlyString() const +{ + mpt::PathString filter; + const auto extensions = GetExtensions(); + { + bool first = true; + for(const auto &ext : extensions) + { + if(first) + { + first = false; + } else + { + filter += P_(";"); + } + filter += P_("*."); + filter += ext; + } + } + return filter; +} + + +mpt::PathString ToFilterString(const FileType &fileType, FlagSet<FileTypeFormat> format) +{ + return fileType.AsFilterString(format); +} + + +mpt::PathString ToFilterString(const std::vector<FileType> &fileTypes, FlagSet<FileTypeFormat> format) +{ + mpt::PathString filter; + for(const auto &type : fileTypes) + { + filter += type.AsFilterString(format); + } + return filter; +} + + +mpt::PathString ToFilterOnlyString(const FileType &fileType, bool prependSemicolonWhenNotEmpty) +{ + mpt::PathString filter = fileType.AsFilterOnlyString(); + return filter.empty() ? filter : (prependSemicolonWhenNotEmpty ? P_(";") : P_("")) + filter; +} + + +mpt::PathString ToFilterOnlyString(const std::vector<FileType> &fileTypes, bool prependSemicolonWhenNotEmpty) +{ + mpt::PathString filter; + for(const auto &type : fileTypes) + { + filter += type.AsFilterOnlyString(); + } + return filter.empty() ? filter : (prependSemicolonWhenNotEmpty ? P_(";") : P_("")) + filter; +} + + +#endif // MODPLUG_TRACKER + + + +OPENMPT_NAMESPACE_END |