Files
UnrealEngine/Engine/Source/Developer/Windows/LiveCodingServer/Private/External/LC_Filesystem.cpp
2025-05-18 13:04:45 +08:00

532 lines
12 KiB
C++

// Copyright 2011-2020 Molecular Matters GmbH, all rights reserved.
#if LC_VERSION == 1
// BEGIN EPIC MOD
//#include PCH_INCLUDE
// END EPIC MOD
#include "LC_Filesystem.h"
#include "LC_CriticalSection.h"
#include "LC_Hashing.h"
// BEGIN EPIC MOD
#include "LC_Logging.h"
// END EPIC MOD
// BEGIN EPIC MOD
#include "Windows/AllowWindowsPlatformTypes.h"
#include <shlwapi.h>
#include "Windows/HideWindowsPlatformTypes.h"
// If PCH files are disabled, this define will cause problems with UnrealString.h
#ifdef PathAppend
#undef PathAppend
#endif
// END EPIC MOD
namespace detail
{
class NormalizedFilenameCache
{
struct Hasher
{
inline size_t operator()(const std::wstring& key) const
{
return Hashing::Hash32(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);
}
Filesystem::Path 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<typename Cache::iterator, bool>& 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
//
// BEGIN EPIC MOD
Filesystem::Path temp;
path = Filesystem::Devirtualize(path, temp);
// END EPIC MOD
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[Filesystem::Path::CAPACITY] = {};
::GetFinalPathNameByHandleW(file, buffer, Filesystem::Path::CAPACITY, 0u);
::CloseHandle(file);
// TODO: Revirtualize?
// the path returned by GetFinalPathNameByHandle starts with "\\?\", cut that off
data.assign(buffer + 4u);
}
else
{
data.assign(path);
}
}
return Filesystem::Path(data.c_str());
}
private:
typedef types::unordered_map_with_hash<std::wstring, std::wstring, Hasher> Cache;
Cache m_data;
CriticalSection m_cs;
};
}
namespace
{
static detail::NormalizedFilenameCache* g_normalizedFilenameCache = nullptr;
static Filesystem::DriveType::Enum g_driveTypeCache['z' - 'a' + 1u] = {};
}
void Filesystem::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 Filesystem::Shutdown(void)
{
delete g_normalizedFilenameCache;
}
Filesystem::DriveType::Enum Filesystem::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;
}
Filesystem::PathAttributes Filesystem::GetAttributes(const wchar_t* path)
{
// BEGIN EPIC MOD
Path temp;
path = Devirtualize(path, temp);
// END EPIC MOD
::WIN32_FILE_ATTRIBUTE_DATA attributes = {};
attributes.dwFileAttributes = INVALID_FILE_ATTRIBUTES;
::GetFileAttributesExW(path, GetFileExInfoStandard, &attributes);
::ULARGE_INTEGER fileSize = {};
fileSize.LowPart = attributes.nFileSizeLow;
fileSize.HighPart = attributes.nFileSizeHigh;
::ULARGE_INTEGER lastModificationTime = {};
lastModificationTime.LowPart = attributes.ftLastWriteTime.dwLowDateTime;
lastModificationTime.HighPart = attributes.ftLastWriteTime.dwHighDateTime;
return PathAttributes { fileSize.QuadPart, lastModificationTime.QuadPart, attributes.dwFileAttributes };
}
uint64_t Filesystem::GetSize(const PathAttributes& attributes)
{
return attributes.size;
}
uint64_t Filesystem::GetLastModificationTime(const PathAttributes& attributes)
{
return attributes.lastModificationTime;
}
bool Filesystem::DoesExist(const PathAttributes& attributes)
{
return (attributes.flags != INVALID_FILE_ATTRIBUTES);
}
bool Filesystem::IsDirectory(const PathAttributes& attributes)
{
if (!DoesExist(attributes))
{
return false;
}
return (attributes.flags & FILE_ATTRIBUTE_DIRECTORY);
}
bool Filesystem::IsRelativePath(const wchar_t* path)
{
// empty paths are not considered to be relative
if (path[0] == L'\0')
{
return false;
}
// BEGIN EPIC MOD
return (::PathIsRelativeW(path) != Windows::FALSE);
// END EPIC MOD
}
void Filesystem::Copy(const wchar_t* srcPath, const wchar_t* destPath)
{
// BEGIN EPIC MOD
Path srcTemp;
srcPath = Devirtualize(srcPath, srcTemp);
Path destTemp;
destPath = Devirtualize(destPath, destTemp);
const BOOL success = ::CopyFileW(srcPath, destPath, Windows::FALSE);
if (success == Windows::FALSE)
// END EPIC MOD
{
LC_ERROR_USER("Failed to copy file from %S to %S. Error: 0x%X", srcPath, destPath, ::GetLastError());
}
}
void Filesystem::Move(const wchar_t* srcPath, const wchar_t* destPath)
{
// BEGIN EPIC MOD
Path srcTemp;
srcPath = Devirtualize(srcPath, srcTemp);
Path destTemp;
destPath = Devirtualize(destPath, destTemp);
// END EPIC MOD
const BOOL success = ::MoveFileExW(srcPath, destPath, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
// BEGIN EPIC MOD
if (success == Windows::FALSE)
// END EPIC MOD
{
LC_ERROR_USER("Failed to move file from %S to %S. Error: 0x%X", srcPath, destPath, ::GetLastError());
}
}
void Filesystem::Delete(const wchar_t* path)
{
// BEGIN EPIC MOD
Path temp;
path = Devirtualize(path, temp);
// END EPIC MOD
const BOOL success = ::DeleteFileW(path);
// BEGIN EPIC MOD
if (success == Windows::FALSE)
// END EPIC MOD
{
LC_ERROR_USER("Failed to delete file %S. Error: 0x%X", path, ::GetLastError());
}
}
bool Filesystem::DeleteIfExists(const wchar_t* path)
{
// BEGIN EPIC MOD
Path temp;
path = Devirtualize(path, temp);
// END EPIC MOD
const BOOL success = ::DeleteFileW(path);
// BEGIN EPIC MOD
return (success != Windows::FALSE);
// END EPIC MOD
}
Filesystem::Path Filesystem::GenerateTempFilename(void)
{
wchar_t path[Filesystem::Path::CAPACITY] = {};
::GetTempPathW(Filesystem::Path::CAPACITY, path);
wchar_t filename[Filesystem::Path::CAPACITY] = {};
wchar_t prefix[1] = { '\0' };
::GetTempFileNameW(path, prefix, 0u, filename);
return Path(filename);
}
Filesystem::Path Filesystem::GetDirectory(const wchar_t* path)
{
unsigned int lastFoundIndex = 0u;
unsigned int index = 0u;
for (/*nothing*/; path[index] != '\0'; ++index)
{
if (path[index] == '\\')
{
lastFoundIndex = index;
}
}
if (lastFoundIndex == 0u)
{
// no directory found, return original path
return Path(path);
}
return Path(path, lastFoundIndex);
}
Filesystem::Path Filesystem::GetFilename(const wchar_t* path)
{
unsigned int lastFoundIndex = 0u;
unsigned int index = 0u;
for (/*nothing*/; path[index] != '\0'; ++index)
{
if (path[index] == '\\')
{
lastFoundIndex = index;
}
}
if (lastFoundIndex == 0u)
{
// no filename found, return original path
return Path(path);
}
return Path(path + lastFoundIndex + 1u, index - lastFoundIndex - 1u);
}
Filesystem::Path Filesystem::GetExtension(const wchar_t* path)
{
unsigned int lastFoundIndex = 0u;
unsigned int index = 0u;
for (/*nothing*/; path[index] != '\0'; ++index)
{
if (path[index] == '.')
{
lastFoundIndex = index;
}
}
if (lastFoundIndex == 0u)
{
// no extension found, return empty path
return Path();
}
return Path(path + lastFoundIndex, index - lastFoundIndex);
}
Filesystem::Path Filesystem::RemoveExtension(const wchar_t* path)
{
unsigned int lastFoundIndex = 0u;
unsigned int index = 0u;
for (/*nothing*/; path[index] != '\0'; ++index)
{
if (path[index] == '.')
{
lastFoundIndex = index;
}
}
if (lastFoundIndex == 0u)
{
// no extension found, return original path
return Path(path);
}
return Path(path, lastFoundIndex);
}
Filesystem::Path Filesystem::NormalizePath(const wchar_t* path)
{
// normalizing files is really costly on Windows, so we cache results
return g_normalizedFilenameCache->UpdateCacheData(path);
}
Filesystem::Path Filesystem::NormalizePathWithoutResolvingLinks(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[Filesystem::Path::CAPACITY] = {};
{
const DWORD charsWritten = ::GetShortPathNameW(path, shortPath, Filesystem::Path::CAPACITY);
if (charsWritten == 0u)
{
return Path(path);
}
}
wchar_t longPath[Filesystem::Path::CAPACITY] = {};
{
const DWORD charsWritten = ::GetLongPathNameW(shortPath, longPath, Filesystem::Path::CAPACITY);
if (charsWritten == 0u)
{
return Path(path);
}
}
return Path(longPath);
}
bool Filesystem::CreateFileWithData(const wchar_t* path, const void* data, size_t size)
{
// BEGIN EPIC MOD
Path temp;
path = Devirtualize(path, temp);
// END EPIC MOD
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<DWORD>(size), &bytesWritten, NULL);
::CloseHandle(file);
return true;
}
types::vector<Filesystem::Path> Filesystem::EnumerateFiles(const wchar_t* directory)
{
types::vector<Path> foundFiles;
foundFiles.reserve(1024u);
HANDLE fileHandle = INVALID_HANDLE_VALUE;
WIN32_FIND_DATAW findData = {};
types::vector<Path> directories;
directories.reserve(1024u);
directories.push_back(Path());
Path findPath;
Path foundPath;
while (!directories.empty())
{
Path subDirectoryOnly = directories.back();
directories.pop_back();
findPath = directory;
findPath += L"\\";
findPath += subDirectoryOnly;
findPath += L"*";
fileHandle = ::FindFirstFileExW(findPath.GetString(), FindExInfoBasic, &findData, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH);
if (fileHandle == INVALID_HANDLE_VALUE)
{
return foundFiles;
}
do
{
// ignore "." and ".."
const bool isDot = (findData.cFileName[0] == L'.') && (findData.cFileName[1] == L'\0');
const bool isDotDot = (findData.cFileName[0] == L'.') && (findData.cFileName[1] == L'.') && (findData.cFileName[2] == L'\0');
if ((!isDot) && (!isDotDot))
{
foundPath = subDirectoryOnly;
foundPath += findData.cFileName;
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
foundPath += L"\\";
directories.push_back(foundPath);
}
else
{
foundFiles.push_back(foundPath);
}
}
}
while (::FindNextFile(fileHandle, &findData) != 0);
const DWORD lastError = GetLastError();
if (lastError != ERROR_NO_MORE_FILES)
{
LC_ERROR_USER("Could not enumerate files in directory %S. Error: %d", directory, lastError);
::FindClose(fileHandle);
return foundFiles;
}
::FindClose(fileHandle);
}
return foundFiles;
}
#endif