// Copyright 2011-2019 Molecular Matters GmbH, all rights reserved. #include "LC_FileUtil.h" #include "LC_CriticalSection.h" #include "LC_Logging.h" #include "xxhash.h" #include #include #pragma comment(lib, "Shlwapi.lib") namespace detail { class NormalizedFilenameCache { struct Hasher { inline size_t operator()(const std::wstring& key) const { return XXH32(key.c_str(), key.length() * sizeof(wchar_t), 0u); } }; public: NormalizedFilenameCache(void) : m_data() , m_cs() { // make space for 128k entries m_data.reserve(128u * 1024u); } std::wstring UpdateCacheData(const wchar_t* path) { CriticalSection::ScopedLock lock(&m_cs); // try to insert the element into the cache. if it exists, return the cached data. // if it doesn't exist, get the file name once and store it. const std::pair& optional = m_data.emplace(std::wstring(path), std::wstring()); std::wstring& data = optional.first->second; if (optional.second) { // value was inserted, update it with the correct data HANDLE file = ::CreateFileW(path, FILE_READ_ATTRIBUTES | STANDARD_RIGHTS_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); if (file != INVALID_HANDLE_VALUE) { wchar_t buffer[MAX_PATH] = {}; ::GetFinalPathNameByHandleW(file, buffer, MAX_PATH, 0u); ::CloseHandle(file); // the path returned by GetFinalPathNameByHandle starts with "\\?\", cut that off data.assign(buffer + 4u); } else { data.assign(path); } } return data; } private: typedef types::unordered_map_with_hash Cache; Cache m_data; CriticalSection m_cs; }; } namespace { static detail::NormalizedFilenameCache* g_normalizedFilenameCache = nullptr; static file::DriveType::Enum g_driveTypeCache['z' - 'a' + 1u] = {}; } void file::Startup(void) { g_normalizedFilenameCache = new detail::NormalizedFilenameCache; // fill cache of drive types char root[4u] = { 'a', ':', '\\', '\0' }; for (char drive = 'a'; drive <= 'z'; ++drive) { const int index = drive - 'a'; root[0] = drive; const UINT driveType = ::GetDriveTypeA(root); switch (driveType) { case DRIVE_UNKNOWN: g_driveTypeCache[index] = DriveType::UNKNOWN; break; case DRIVE_NO_ROOT_DIR: g_driveTypeCache[index] = DriveType::UNKNOWN; break; case DRIVE_REMOVABLE: g_driveTypeCache[index] = DriveType::REMOVABLE; break; case DRIVE_FIXED: g_driveTypeCache[index] = DriveType::FIXED; break; case DRIVE_REMOTE: g_driveTypeCache[index] = DriveType::REMOTE; break; case DRIVE_CDROM: g_driveTypeCache[index] = DriveType::OPTICAL; break; case DRIVE_RAMDISK: g_driveTypeCache[index] = DriveType::RAMDISK; break; default: g_driveTypeCache[index] = DriveType::UNKNOWN; break; } } } void file::Shutdown(void) { delete g_normalizedFilenameCache; } file::DriveType::Enum file::GetDriveType(char driveLetter) { if ((driveLetter >= 'a') && (driveLetter <= 'z')) { return g_driveTypeCache[driveLetter - 'a']; } else if ((driveLetter >= 'A') && (driveLetter <= 'Z')) { return g_driveTypeCache[driveLetter - 'A']; } return DriveType::UNKNOWN; } file::DriveType::Enum file::GetDriveType(const wchar_t* path) { const wchar_t driveLetter = path[0]; if ((driveLetter >= L'a') && (driveLetter <= L'z')) { return g_driveTypeCache[driveLetter - L'a']; } else if ((driveLetter >= L'A') && (driveLetter <= L'Z')) { return g_driveTypeCache[driveLetter - L'A']; } return DriveType::UNKNOWN; } file::Attributes file::GetAttributes(const wchar_t* path) { const WIN32_FILE_ATTRIBUTE_DATA initialData = { INVALID_FILE_ATTRIBUTES }; Attributes attributes = { initialData }; ::GetFileAttributesExW(path, GetFileExInfoStandard, &attributes.data); return attributes; } uint64_t file::GetLastModificationTime(const Attributes& attributes) { ::ULARGE_INTEGER integer = {}; integer.LowPart = attributes.data.ftLastWriteTime.dwLowDateTime; integer.HighPart = attributes.data.ftLastWriteTime.dwHighDateTime; return static_cast(integer.QuadPart); } bool file::DoesExist(const Attributes& attributes) { return (attributes.data.dwFileAttributes != INVALID_FILE_ATTRIBUTES); } bool file::IsDirectory(const Attributes& attributes) { if (!DoesExist(attributes)) { return false; } return (attributes.data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); } uint64_t file::GetSize(const Attributes& attributes) { ::ULARGE_INTEGER integer = {}; integer.LowPart = attributes.data.nFileSizeLow; integer.HighPart = attributes.data.nFileSizeHigh; return static_cast(integer.QuadPart); } void file::Copy(const wchar_t* srcPath, const wchar_t* destPath) { const BOOL success = ::CopyFileW(srcPath, destPath, Windows::FALSE); if (success == Windows::FALSE) { LC_ERROR_USER("Failed to copy file from %S to %S. Error: 0x%X", srcPath, destPath, ::GetLastError()); } } void file::Delete(const wchar_t* path) { const BOOL success = ::DeleteFileW(path); if (success == Windows::FALSE) { LC_ERROR_USER("Failed to delete file %S. Error: 0x%X", path, ::GetLastError()); } } bool file::DeleteIfExists(const wchar_t* path) { const BOOL success = ::DeleteFileW(path); return (success != Windows::FALSE); } bool file::IsRelativePath(const wchar_t* path) { // empty paths are not considered to be relative if (path[0] == L'\0') { return false; } return (::PathIsRelativeW(path) != Windows::FALSE); } std::wstring file::GenerateTempFilename(void) { wchar_t path[MAX_PATH] = {}; const DWORD pathLength = ::GetTempPathW(MAX_PATH, path); wchar_t filename[MAX_PATH] = {}; wchar_t prefix[1] = { '\0' }; ::GetTempFileNameW(path, prefix, 0u, filename); return std::wstring(filename); } bool file::CreateFileWithData(const wchar_t* path, const void* data, size_t size) { HANDLE file = ::CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (file == INVALID_HANDLE_VALUE) { LC_ERROR_USER("Cannot open file %S for writing. Error: 0x%X", path, ::GetLastError()); return false; } DWORD bytesWritten = 0u; ::WriteFile(file, data, static_cast(size), &bytesWritten, NULL); ::CloseHandle(file); return true; } std::wstring file::GetDirectory(const std::wstring& path) { const size_t lastDelimiter = path.find_last_of('\\'); if (lastDelimiter != std::wstring::npos) { const std::wstring dir(path.c_str(), path.c_str() + lastDelimiter); return dir; } return path; } std::wstring file::GetFilename(const std::wstring& path) { const size_t lastDelimiter = path.find_last_of('\\'); if (lastDelimiter != std::wstring::npos) { const std::wstring filename(path.c_str() + lastDelimiter + 1u, path.c_str() + path.length()); return filename; } return path; } std::wstring file::GetExtension(const std::wstring& path) { const size_t extensionDot = path.find_last_of('.'); if (extensionDot == std::wstring::npos) { return std::wstring(L""); } const std::wstring filename(path.c_str() + extensionDot, path.c_str() + path.length()); return filename; } std::wstring file::RemoveExtension(const std::wstring& path) { const size_t extensionDot = path.find_last_of('.'); if (extensionDot == std::wstring::npos) { return path; } const std::wstring filename(path.c_str(), path.c_str() + extensionDot); return filename; } std::wstring file::NormalizePath(const wchar_t* path) { // normalizing files is really costly on Windows, so we cache results return g_normalizedFilenameCache->UpdateCacheData(path); } std::wstring file::NormalizePathWithoutLinks(const wchar_t* path) { // use the old trick of converting to short and to long path names to get a path with correct casing wchar_t shortPath[MAX_PATH] = {}; { const DWORD charsWritten = ::GetShortPathNameW(path, shortPath, MAX_PATH); if (charsWritten == 0u) { return path; } } wchar_t longPath[MAX_PATH] = {}; { const DWORD charsWritten = ::GetLongPathNameW(shortPath, longPath, MAX_PATH); if (charsWritten == 0u) { return path; } } return longPath; } std::wstring file::RelativeToAbsolutePath(const wchar_t* path) { wchar_t* absolutePath = _wfullpath(NULL, path, MAX_PATH); if (absolutePath) { std::wstring result(absolutePath); free(absolutePath); return result; } return std::wstring(path); } void file::Move(const wchar_t* currentPath, const wchar_t* movedToPath) { const BOOL success = ::MoveFileExW(currentPath, movedToPath, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH); if (success == Windows::FALSE) { LC_ERROR_USER("Failed to move file from %S to %S. Error: 0x%X", currentPath, movedToPath, ::GetLastError()); } } types::vector file::EnumerateFiles(const wchar_t* directory) { types::vector files; files.reserve(1024u); HANDLE findHandle = INVALID_HANDLE_VALUE; WIN32_FIND_DATAW findData = {}; std::wstring path(directory); std::wstring searchTerm; searchTerm.reserve(MAX_PATH); // recursively walk through directories, enumerating all files from a directory first, // pushing found directories onto a stack, walking those directories until no more files // can be found. std::stack directories; directories.push(path); while (!directories.empty()) { path = directories.top(); directories.pop(); searchTerm = path; searchTerm += L"\\*.*"; findHandle = ::FindFirstFileW(searchTerm.c_str(), &findData); if (findHandle == INVALID_HANDLE_VALUE) { return files; } do { if (wcscmp(findData.cFileName, L".") != 0 && wcscmp(findData.cFileName, L"..") != 0) { std::wstring newPath; newPath.reserve(MAX_PATH); newPath = path; newPath += L"\\"; newPath += findData.cFileName; if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { directories.push(newPath); } else { files.push_back(newPath); } } } while (::FindNextFile(findHandle, &findData) != 0); if (GetLastError() != ERROR_NO_MORE_FILES) { ::FindClose(findHandle); return files; } ::FindClose(findHandle); findHandle = INVALID_HANDLE_VALUE; } return files; }