nxemu/src/common/path.cpp

758 lines
19 KiB
C++

#include "path.h"
#include "std_string.h"
#include <Windows.h>
#include <CommDlg.h>
#include <io.h>
#include <shlobj_core.h>
const char DRIVE_DELIMITER = ':';
const char * const DIR_DOUBLEDELIM = "\\\\";
const char DIRECTORY_DELIMITER = '\\';
const char DIRECTORY_DELIMITER2 = '/';
const char EXTENSION_DELIMITER = '.';
void * Path::m_hInst = nullptr;
Path::Path()
{
}
Path::Path(const Path & path)
{
m_path = path.m_path;
}
Path::Path(const Path & path, const char * fileName)
{
SetDriveDirectory(path.m_path.c_str());
SetNameExtension(fileName);
}
Path::Path(const char * path)
{
m_path = path ? path : "";
CleanPath(m_path);
}
Path::Path(const char * path, const char * fileName)
{
SetDriveDirectory(path);
SetNameExtension(fileName);
}
Path::Path(const std::string & path)
{
m_path = path;
CleanPath(m_path);
}
Path::Path(const std::string & path, const char * fileName)
{
SetDriveDirectory(path.c_str());
SetNameExtension(fileName);
}
Path::Path(const std::string & path, const std::string & fileName)
{
SetDriveDirectory(path.c_str());
SetNameExtension(fileName.c_str());
}
Path::Path(DIR_CURRENT_DIRECTORY /*sdt*/, const char * nameExten)
{
SetToCurrentDirectory();
if (nameExten)
{
SetNameExtension(nameExten);
}
}
Path::Path(DIR_MODULE_DIRECTORY /*sdt*/, const char * nameExten)
{
SetToModuleDirectory();
SetNameExtension(nameExten ? nameExten : "");
}
Path::~Path()
{
CloseFindHandle();
}
Path & Path::operator=(const Path & path)
{
if (this != &path)
{
m_path = path.m_path;
}
return *this;
}
Path::operator const char *() const
{
return (const char *)m_path.c_str();
}
Path::operator const std::string &()
{
return m_path;
}
void Path::GetDriveDirectory(std::string & driveDirectory) const
{
std::string drive, directory;
GetComponents(&drive, &directory);
driveDirectory = drive;
if (!drive.empty())
{
driveDirectory += DRIVE_DELIMITER;
}
driveDirectory += directory;
}
std::string Path::GetDriveDirectory(void) const
{
std::string driveDirectory;
GetDriveDirectory(driveDirectory);
return driveDirectory;
}
void Path::GetDirectory(std::string & directory) const
{
GetComponents(nullptr, &directory);
}
std::string Path::GetDirectory(void) const
{
std::string directory;
GetDirectory(directory);
return directory;
}
void Path::GetNameExtension(std::string & nameExtension) const
{
std::string name, extension;
GetComponents(nullptr, nullptr, &name, &extension);
nameExtension = name;
if (!extension.empty())
{
nameExtension += EXTENSION_DELIMITER;
nameExtension += extension;
}
}
std::string Path::GetNameExtension(void) const
{
std::string nameExtension;
GetNameExtension(nameExtension);
return nameExtension;
}
void Path::GetExtension(std::string & extension) const
{
GetComponents(nullptr, nullptr, nullptr, &extension);
}
std::string Path::GetExtension(void) const
{
std::string extension;
GetExtension(extension);
return extension;
}
void Path::GetComponents(std::string * drive, std::string * directory, std::string * name, std::string * extension) const
{
char driveBuff[_MAX_DRIVE + 1] = {0}, dirBuff[_MAX_DIR + 1] = {0}, nameBuff[_MAX_FNAME + 1] = {0}, extBuff[_MAX_EXT + 1] = {0};
const char * basePath = m_path.c_str();
const char * driveDir = strrchr(basePath, DRIVE_DELIMITER);
if (driveDir != nullptr)
{
size_t len = sizeof(dirBuff) < (driveDir - basePath) ? sizeof(driveBuff) : driveDir - basePath;
strncpy(driveBuff, basePath, len);
basePath += len + 1;
}
const char * last = strrchr(basePath, DIRECTORY_DELIMITER);
if (last != nullptr)
{
size_t len = sizeof(dirBuff) < (last - basePath) ? sizeof(dirBuff) : last - basePath;
if (len > 0)
{
strncpy(dirBuff, basePath, len);
}
else
{
dirBuff[0] = DIRECTORY_DELIMITER;
dirBuff[1] = '\0';
}
strncpy(nameBuff, last + 1, sizeof(nameBuff));
}
else
{
strncpy(dirBuff, basePath, sizeof(dirBuff));
}
char * ext = strrchr(nameBuff, '.');
if (ext != nullptr)
{
strncpy(extBuff, ext + 1, sizeof(extBuff));
*ext = '\0';
}
if (drive)
{
*drive = driveBuff;
}
if (directory)
{
*directory = dirBuff;
}
if (name)
{
*name = nameBuff;
}
if (extension)
{
*extension = extBuff;
}
}
bool Path::IsRelative() const
{
if (m_path.length() > 1 && m_path[1] == DRIVE_DELIMITER)
{
return false;
}
if (m_path.length() > 2 && m_path[0] == DIRECTORY_DELIMITER && m_path[1] == DIRECTORY_DELIMITER)
{
return false;
}
return true;
}
void Path::SetDriveDirectory(const char * driveDirectory)
{
std::string driveDirectoryStr = driveDirectory;
if (driveDirectoryStr.length() > 0)
{
EnsureTrailingBackslash(driveDirectoryStr);
CleanPath(driveDirectoryStr);
}
std::string name, extension;
GetComponents(nullptr, nullptr, &name, &extension);
SetComponents(nullptr, driveDirectoryStr.c_str(), name.c_str(), extension.c_str());
}
void Path::SetDirectory(const char * directory, bool ensureAbsolute)
{
std::string newDirectory = directory;
if (ensureAbsolute)
{
EnsureLeadingBackslash(newDirectory);
}
if (newDirectory.length() > 0)
{
EnsureTrailingBackslash(newDirectory);
}
std::string drive, name, extension;
GetComponents(&drive, nullptr, &name, &extension);
SetComponents(drive.c_str(), newDirectory.c_str(), name.c_str(), extension.c_str());
}
void Path::SetNameExtension(const char * nameExtension)
{
std::string directory, drive;
GetComponents(&drive, &directory);
SetComponents(drive.c_str(), directory.c_str(), nameExtension, nullptr);
}
void Path::SetComponents(const char * drive, const char * directory, const char * name, const char * extension)
{
char fullname[MAX_PATH];
memset(fullname, 0, sizeof(fullname));
if (directory == nullptr || strlen(directory) == 0)
{
static char emptyDir[] = {DIRECTORY_DELIMITER, '\0'};
directory = emptyDir;
}
_makepath(fullname, drive, directory, name, extension);
m_path = fullname;
}
void Path::AppendDirectory(const char * subDirectory)
{
std::string newSubDirectory = subDirectory;
if (newSubDirectory.empty())
{
return;
}
StripLeadingBackslash(newSubDirectory);
EnsureTrailingBackslash(newSubDirectory);
std::string drive, directory, name, extension;
GetComponents(&drive, &directory, &name, &extension);
EnsureTrailingBackslash(directory);
directory += newSubDirectory;
SetComponents(drive.c_str(), directory.c_str(), name.c_str(), extension.c_str());
}
bool Path::FileDelete(bool evenIfReadOnly) const
{
std::wstring filePath = stdstr(m_path).ToUTF16();
uint32_t attributes = ::GetFileAttributes(filePath.c_str());
if (attributes == (uint32_t)-1)
{
return false;
}
if (((attributes & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY) && !evenIfReadOnly)
{
return false;
}
SetFileAttributes(filePath.c_str(), FILE_ATTRIBUTE_NORMAL);
return DeleteFile(filePath.c_str()) != 0;
}
bool Path::FileExists() const
{
WIN32_FIND_DATA FindData;
HANDLE hFindFile = FindFirstFile(stdstr(m_path).ToUTF16().c_str(), &FindData);
bool bSuccess = (hFindFile != INVALID_HANDLE_VALUE);
if (hFindFile != nullptr) // Make sure we close the search
{
FindClose(hFindFile);
}
return bSuccess;
}
bool Path::FileSelect(void * hwndOwner, const char * initialDir, const char * fileFilter, bool fileMustExist)
{
size_t filterLen = 0;
while (fileFilter[filterLen] != '\0' || fileFilter[filterLen + 1] != '\0')
{
filterLen++;
}
filterLen += 2;
std::vector<wchar_t> fileFilterW(filterLen);
MultiByteToWideChar(CP_UTF8, 0, fileFilter, (int)filterLen, fileFilterW.data(), static_cast<int>(filterLen));
Path currentDir(CURRENT_DIRECTORY);
std::wstring initialDirW = stdstr(initialDir).ToUTF16();
OPENFILENAME openfilename = {};
std::vector<wchar_t> fileName(32768);
openfilename.lStructSize = sizeof(openfilename);
openfilename.hwndOwner = (HWND)hwndOwner;
openfilename.lpstrFilter = fileFilterW.data();
openfilename.lpstrFile = fileName.data();
openfilename.lpstrInitialDir = initialDirW.c_str();
openfilename.nMaxFile = (DWORD)fileName.size();
openfilename.Flags = OFN_HIDEREADONLY | (fileMustExist ? OFN_FILEMUSTEXIST : 0);
bool res = GetOpenFileName(&openfilename) != 0;
if (Path(CURRENT_DIRECTORY) != currentDir)
{
currentDir.DirectoryChange();
}
if (!res)
{
return false;
}
m_path = stdstr().FromUTF16(fileName.data());
CleanPath(m_path);
return true;
}
Path & Path::BrowseForDirectory(void * parentWindow, const char * title)
{
*this = Path();
const HRESULT hrCo = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
IFileDialog* dlg = nullptr;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&dlg));
if (SUCCEEDED(hr))
{
DWORD options = 0;
if (SUCCEEDED(dlg->GetOptions(&options)))
{
options |= FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR;
dlg->SetOptions(options);
}
if (title && *title)
{
dlg->SetTitle(stdstr_f(title).ToUTF16().c_str());
}
hr = dlg->Show((HWND)parentWindow);
if (SUCCEEDED(hr))
{
IShellItem* result = nullptr;
if (SUCCEEDED(dlg->GetResult(&result)) && result)
{
PWSTR psz = nullptr;
if (SUCCEEDED(result->GetDisplayName(SIGDN_FILESYSPATH, &psz)) && psz)
{
*this = Path(stdstr().FromUTF16(psz).c_str(), "");
CoTaskMemFree(psz);
}
result->Release();
}
}
dlg->Release();
}
if (SUCCEEDED(hrCo))
{
CoUninitialize();
}
return *this;
}
bool Path::IsDirectory() const
{
std::string fileName;
GetNameExtension(fileName);
return fileName.empty();
}
bool Path::DirectoryCreate(bool createIntermediates)
{
if (DirectoryExists())
{
return true;
}
std::string path;
GetDriveDirectory(path);
StripTrailingBackslash(path);
bool bSuccess = ::CreateDirectory(stdstr(path).ToUTF16().c_str(), nullptr) != 0;
if (!bSuccess && createIntermediates)
{
std::string::size_type delimiter = path.rfind(DIRECTORY_DELIMITER);
if (delimiter == std::string::npos)
{
return false;
}
path.resize(delimiter + 1);
return Path(path).DirectoryCreate() ? DirectoryCreate(false) : false;
}
return bSuccess;
}
bool Path::DirectoryChange() const
{
std::string driveDirectory;
GetDriveDirectory(driveDirectory);
return SetCurrentDirectory(stdstr(driveDirectory).ToUTF16().c_str()) != 0;
}
bool Path::DirectoryExists() const
{
Path path(m_path.c_str());
std::string directory;
path.DirectoryUp(&directory);
path.SetNameExtension(directory.c_str());
WIN32_FIND_DATA FindData;
HANDLE findFile = FindFirstFile(stdstr((const char *)path).ToUTF16().c_str(), &FindData);
bool res = (findFile != INVALID_HANDLE_VALUE);
if (findFile != INVALID_HANDLE_VALUE)
{
FindClose(findFile);
}
return res;
}
Path & Path::DirectoryNormalize(Path BaseDir)
{
stdstr directory = BaseDir.GetDriveDirectory();
bool changed = false;
if (IsRelative())
{
EnsureTrailingBackslash(directory);
directory += GetDirectory();
changed = true;
}
strvector parts = directory.Tokenize(DIRECTORY_DELIMITER);
strvector normalizesParts;
for (strvector::const_iterator itr = parts.begin(); itr != parts.end(); itr++)
{
if (*itr == ".")
{
changed = true;
}
else if (*itr == "..")
{
normalizesParts.pop_back();
changed = true;
}
else
{
normalizesParts.push_back(*itr);
}
}
if (changed)
{
directory.clear();
for (strvector::const_iterator itr = normalizesParts.begin(); itr != normalizesParts.end(); itr++)
{
directory += *itr + DIRECTORY_DELIMITER;
}
SetDriveDirectory(directory.c_str());
}
return *this;
}
void Path::DirectoryUp(std::string * lastDir)
{
std::string directory;
GetDirectory(directory);
StripTrailingBackslash(directory);
if (directory.empty())
{
return;
}
std::string::size_type delimiter = directory.rfind(DIRECTORY_DELIMITER);
if (lastDir != nullptr)
{
*lastDir = directory.substr(delimiter);
StripLeadingBackslash(*lastDir);
}
if (delimiter != std::string::npos)
{
directory = directory.substr(0, delimiter);
}
SetDirectory(directory.c_str());
}
bool Path::FindFirst(uint32_t attributes)
{
CloseFindHandle();
m_findAttributes = attributes;
bool bWantSubdirectory = (FIND_ATTRIBUTE_SUBDIR & attributes) != 0;
WIN32_FIND_DATA findData;
m_findHandle = FindFirstFile(stdstr(m_path).ToUTF16().c_str(), &findData);
bool foundFile = (m_findHandle != INVALID_HANDLE_VALUE);
if (m_findHandle == INVALID_HANDLE_VALUE)
{
m_findHandle = nullptr;
}
while (foundFile)
{
if (AttributesMatch(m_findAttributes, findData.dwFileAttributes) &&
(!bWantSubdirectory || (findData.cFileName[0] != '.')))
{
if ((FIND_ATTRIBUTE_SUBDIR & findData.dwFileAttributes) != 0)
{
StripTrailingBackslash(m_path);
}
SetNameExtension(stdstr().FromUTF16(findData.cFileName).c_str());
if ((FIND_ATTRIBUTE_SUBDIR & findData.dwFileAttributes) != 0)
{
EnsureTrailingBackslash(m_path);
}
return true;
}
foundFile = FindNextFile(m_findHandle, &findData);
}
return false;
}
bool Path::FindNext()
{
if (m_findHandle == nullptr)
{
return false;
}
WIN32_FIND_DATA FindData;
while (FindNextFile(m_findHandle, &FindData) != false)
{
if (AttributesMatch(m_findAttributes, FindData.dwFileAttributes))
{
if ((_A_SUBDIR & FindData.dwFileAttributes) == _A_SUBDIR)
{
if (IsDirectory())
{
DirectoryUp();
}
else
{
SetNameExtension("");
}
AppendDirectory(stdstr().FromUTF16(FindData.cFileName).c_str());
}
else
{
if (IsDirectory())
{
DirectoryUp();
}
SetNameExtension(stdstr().FromUTF16(FindData.cFileName).c_str());
}
if ((_A_SUBDIR & FindData.dwFileAttributes) == _A_SUBDIR)
{
EnsureTrailingBackslash(m_path);
}
return true;
}
}
return false;
}
void Path::SetToCurrentDirectory()
{
DWORD required = ::GetCurrentDirectory(0, nullptr);
if (required == 0)
{
m_path = "";
return;
}
std::vector<wchar_t> path(required);
DWORD result = ::GetCurrentDirectory(required, path.data());
if (result == 0 || result > required)
{
m_path = "";
return;
}
SetDriveDirectory(stdstr().FromUTF16(path.data()).c_str());
}
void Path::SetToModuleDirectory()
{
DWORD bufferSize = MAX_PATH;
std::vector<wchar_t> buffPath(bufferSize);
while (true)
{
DWORD result = GetModuleFileNameW((HINSTANCE)m_hInst, buffPath.data(), bufferSize);
if (result == 0)
{
m_path = "";
return;
}
if (result < bufferSize)
{
m_path = stdstr().FromUTF16(buffPath.data());
SetNameExtension("");
return;
}
bufferSize *= 2;
buffPath.resize(bufferSize);
}
}
bool Path::AttributesMatch(uint32_t targetAttributes, uint32_t fileAttributes)
{
if (targetAttributes == FIND_ATTRIBUTE_ALLFILES)
{
return true;
}
if (targetAttributes == FIND_ATTRIBUTE_FILES)
{
return ((FIND_ATTRIBUTE_SUBDIR & fileAttributes) == 0);
}
return (((targetAttributes & fileAttributes) != 0) && ((FIND_ATTRIBUTE_SUBDIR & targetAttributes) == (FIND_ATTRIBUTE_SUBDIR & fileAttributes)));
}
void Path::CloseFindHandle()
{
if (m_findHandle != nullptr)
{
FindClose(m_findHandle);
m_findHandle = nullptr;
}
}
void Path::CleanPath(std::string & path) const
{
std::string::size_type pos = path.find(DIRECTORY_DELIMITER2);
while (pos != std::string::npos)
{
path.replace(pos, 1, &DIRECTORY_DELIMITER);
pos = path.find(DIRECTORY_DELIMITER2, pos + 1);
}
bool appendEnd = !_strnicmp(path.c_str(), DIR_DOUBLEDELIM, 2);
pos = path.find(DIR_DOUBLEDELIM);
while (pos != std::string::npos)
{
path.replace(pos, 2, &DIRECTORY_DELIMITER);
pos = path.find(DIR_DOUBLEDELIM, pos + 1);
}
if (appendEnd)
{
path.insert(0, stdstr_f("%c", DIRECTORY_DELIMITER).c_str());
}
}
void Path::EnsureLeadingBackslash(std::string & directory) const
{
if (directory.empty() || (directory[0] != DIRECTORY_DELIMITER))
{
directory = stdstr_f("%c%s", DIRECTORY_DELIMITER, directory.c_str());
}
}
void Path::EnsureTrailingBackslash(std::string & directory) const
{
std::string::size_type length = directory.length();
if (directory.empty() || (directory[length - 1] != DIRECTORY_DELIMITER))
{
directory += DIRECTORY_DELIMITER;
}
}
void Path::StripLeadingBackslash(std::string & directory) const
{
if (directory.length() <= 1)
{
return;
}
if (directory[0] == DIRECTORY_DELIMITER)
{
directory = directory.substr(1);
}
}
void Path::StripTrailingBackslash(std::string & directory) const
{
for (;;)
{
std::string::size_type length = directory.length();
if (length <= 1)
{
break;
}
if (directory[length - 1] == DIRECTORY_DELIMITER || directory[length - 1] == DIRECTORY_DELIMITER2)
{
directory.resize(length - 1);
continue;
}
break;
}
}