From 31da561695a653f2b6c31e179bbcadf45a31599f Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Fri, 10 Jul 2020 18:32:23 +0200 Subject: [PATCH] Complete UTF-16 (Unicode) support added for Windows. ROM names with Unicode characters are supported, as well as running ES from a directory that has Unicode characters in its name. --- NEWS.md | 2 +- es-app/src/CollectionSystemManager.cpp | 19 ++- es-app/src/FileData.cpp | 6 +- es-app/src/Gamelist.cpp | 15 +++ es-app/src/SystemData.cpp | 4 + es-app/src/SystemData.h | 6 +- es-app/src/main.cpp | 9 +- es-app/src/scrapers/Scraper.cpp | 11 ++ es-core/src/InputManager.cpp | 23 +++- es-core/src/Log.cpp | 34 +++--- es-core/src/Log.h | 3 - es-core/src/MameNames.cpp | 13 +++ es-core/src/Platform.cpp | 4 +- es-core/src/Settings.cpp | 9 ++ es-core/src/ThemeData.cpp | 9 ++ es-core/src/resources/ResourceManager.cpp | 5 + es-core/src/utils/FileSystemUtil.cpp | 136 +++++++++++++++++----- es-core/src/utils/FileSystemUtil.h | 3 + es-core/src/utils/StringUtil.cpp | 4 +- es-core/src/utils/StringUtil.h | 4 +- 20 files changed, 251 insertions(+), 68 deletions(-) diff --git a/NEWS.md b/NEWS.md index bfde8f25f..dcba97dce 100644 --- a/NEWS.md +++ b/NEWS.md @@ -13,7 +13,7 @@ v1.0.0 * Full navigation sound support, configurable per theme * New default theme rbsimple-DE bundled with the software, this theme is largely based on recalbox-multi by the Recalbox community * Added extensive es_systems.cfg templates for Unix and Windows -* Updated the application to compile and work on Microsoft Windows +* Updated the application to compile and work on Microsoft Windows, including full UTF-16 (Unicode) support * Seamless (almost) launch of games without showing the desktop when starting and returning from RetroArch and other emulators * Per-game launch command override, so that different cores or emulators can be used on a per-game basis (saved to gamelist.xml) * Core location can be defined relative to the emulator binary using the %EMUPATH% varible in es_systems.cfg (mostly useful for Windows) diff --git a/es-app/src/CollectionSystemManager.cpp b/es-app/src/CollectionSystemManager.cpp index 3811ebecd..85f4aa993 100644 --- a/es-app/src/CollectionSystemManager.cpp +++ b/es-app/src/CollectionSystemManager.cpp @@ -128,10 +128,15 @@ void CollectionSystemManager::saveCustomCollection(SystemData* sys) CollectionSystemData sysData = mCustomCollectionSystemsData.at(name); if (sysData.needsSave) { std::ofstream configFile; + #ifdef _WIN64 + configFile.open(Utils::String:: + stringToWideString(getCustomCollectionConfigPath(name)).c_str()); + #else configFile.open(getCustomCollectionConfigPath(name)); + #endif for (std::unordered_map::const_iterator iter = games.cbegin(); iter != games.cend(); ++iter) { - std::string path = iter->first; + std::string path = iter->first; // If the ROM path of the game begins with the path from the setting // ROMDirectory (or the default ROM directory), then replace it with %ROMPATH%. if (path.find(rompath) == 0) @@ -143,7 +148,7 @@ void CollectionSystemManager::saveCustomCollection(SystemData* sys) } } else { - LOG(LogError) << "Couldn't find collection to save! " << name; + LOG(LogError) << "Error - Couldn't find collection to save! " << name; } } @@ -838,7 +843,7 @@ void CollectionSystemManager::populateAutoCollection(CollectionSystemData* sysDa sysData->isPopulated = true; } -// Populate a custom collection system +// Populate a custom collection system. void CollectionSystemManager::populateCustomCollection(CollectionSystemData* sysData) { SystemData* newSys = sysData->system; @@ -856,7 +861,11 @@ void CollectionSystemManager::populateCustomCollection(CollectionSystemData* sys FileFilterIndex* index = newSys->getIndex(); // Get configuration for this custom collection. + #if _WIN64 + std::ifstream input(Utils::String::stringToWideString(path).c_str()); + #else std::ifstream input(path); + #endif // Get all files map. std::unordered_map @@ -970,7 +979,11 @@ std::vector CollectionSystemManager::getSystemsFromConfig() return systems; pugi::xml_document doc; + #ifdef _WIN64 + pugi::xml_parse_result res = doc.load_file(Utils::String::stringToWideString(path).c_str()); + #else pugi::xml_parse_result res = doc.load_file(path.c_str()); + #endif if (!res) return systems; diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index e8da04263..f6df6cc25 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -465,7 +465,7 @@ void FileData::launchGame(Window* window) command = Utils::String::replace(command, "%ROM_RAW%", rom_raw); #ifdef _WIN64 - std::wstring commandWide = Utils::String::charToWideChar(command); + std::wstring commandWide = Utils::String::stringToWideString(command); #endif Scripting::fireEvent("game-start", rom, basename); @@ -494,7 +494,7 @@ void FileData::launchGame(Window* window) std::wstring emuExecutableWide; std::wstring emuPathWide; - emuExecutableWide = Utils::String::charToWideChar(emuExecutable); + emuExecutableWide = Utils::String::stringToWideString(emuExecutable); // Search for the emulator using the PATH environmental variable. DWORD size = SearchPathW(nullptr, emuExecutableWide.c_str(), L".exe", 0, nullptr, nullptr); @@ -525,7 +525,7 @@ void FileData::launchGame(Window* window) LOG(LogInfo) << "Expanded emulator launch command:"; #ifdef _WIN64 - LOG(LogInfo) << Utils::String::wideCharToChar(commandWide); + LOG(LogInfo) << Utils::String::wideStringToString(commandWide); exitCode = launchEmulatorWindows(commandWide); #else LOG(LogInfo) << command; diff --git a/es-app/src/Gamelist.cpp b/es-app/src/Gamelist.cpp index 6e5359638..c4b026c1d 100644 --- a/es-app/src/Gamelist.cpp +++ b/es-app/src/Gamelist.cpp @@ -9,6 +9,7 @@ #include #include "utils/FileSystemUtil.h" +#include "utils/StringUtil.h" #include "FileData.h" #include "FileFilterIndex.h" #include "Log.h" @@ -93,7 +94,12 @@ void parseGamelist(SystemData* system) LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"..."; pugi::xml_document doc; + #ifdef _WIN64 + pugi::xml_parse_result result = + doc.load_file(Utils::String::stringToWideString(xmlpath).c_str()); + #else pugi::xml_parse_result result = doc.load_file(xmlpath.c_str()); + #endif if (!result) { LOG(LogError) << "Error parsing XML file \"" << xmlpath << @@ -189,7 +195,12 @@ void updateGamelist(SystemData* system) if (Utils::FileSystem::exists(xmlReadPath)) { // Parse an existing file first. + #ifdef _WIN64 + pugi::xml_parse_result result = + doc.load_file(Utils::String::stringToWideString(xmlReadPath).c_str()); + #else pugi::xml_parse_result result = doc.load_file(xmlReadPath.c_str()); + #endif if (!result) { LOG(LogError) << "Error parsing XML file \"" << xmlReadPath << "\"!\n " << @@ -264,7 +275,11 @@ void updateGamelist(SystemData* system) LOG(LogInfo) << "Added/Updated " << numUpdated << " entities in '" << xmlReadPath << "'"; + #ifdef _WIN64 + if (!doc.save_file(Utils::String::stringToWideString(xmlWritePath).c_str())) { + #else if (!doc.save_file(xmlWritePath.c_str())) { + #endif LOG(LogError) << "Error saving gamelist.xml to \"" << xmlWritePath << "\" (for system " << system->getName() << ")!"; } diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index 2866603c5..e7fca0e47 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -226,7 +226,11 @@ bool SystemData::loadConfig() } pugi::xml_document doc; + #ifdef _WIN64 + pugi::xml_parse_result res = doc.load_file(Utils::String::stringToWideString(path).c_str()); + #else pugi::xml_parse_result res = doc.load_file(path.c_str()); + #endif if (!res) { LOG(LogError) << "Error - Could not parse es_systems.cfg"; diff --git a/es-app/src/SystemData.h b/es-app/src/SystemData.h index 076911919..e2f6de484 100644 --- a/es-app/src/SystemData.h +++ b/es-app/src/SystemData.h @@ -21,8 +21,7 @@ class FileData; class FileFilterIndex; class ThemeData; -struct SystemEnvironmentData -{ +struct SystemEnvironmentData { std::string mStartPath; std::vector mSearchExtensions; std::string mLaunchCommand; @@ -32,7 +31,8 @@ struct SystemEnvironmentData class SystemData { public: - SystemData(const std::string& name, + SystemData( + const std::string& name, const std::string& fullName, SystemEnvironmentData* envData, const std::string& themeFolder, diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp index bd0ea7993..275455b9d 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -19,6 +19,7 @@ #include "guis/GuiDetectDevice.h" #include "guis/GuiMsgBox.h" #include "utils/FileSystemUtil.h" +#include "utils/StringUtil.h" #include "views/ViewController.h" #include "CollectionSystemManager.h" #include "EmulationStation.h" @@ -342,8 +343,7 @@ bool loadSystemConfigFile(std::string& errorMsg) return true; } - if (SystemData::sSystemVector.size() == 0) - { + if (SystemData::sSystemVector.size() == 0) { LOG(LogError) << "Error - No systems found, does at least one system have a game present? " "(Check that the file extensions are supported.)"; errorMsg = "THE SYSTEMS CONFIGURATION FILE EXISTS, BUT NO\n" @@ -354,7 +354,12 @@ bool loadSystemConfigFile(std::string& errorMsg) "THE GAME SYSTEMS SUBDIRECTORIES ALSO NEED TO\n" "MATCH THE PLATFORM TAGS IN ES_SYSTEMS.CFG.\n" "THIS IS THE CURRENTLY CONFIGURED ROM DIRECTORY:\n"; + #ifdef _WIN64 + errorMsg += Utils::String::replace(FileData::getROMDirectory(), "/", "\\"); + #else errorMsg += FileData::getROMDirectory(); + #endif + return true; } diff --git a/es-app/src/scrapers/Scraper.cpp b/es-app/src/scrapers/Scraper.cpp index 879da3fa8..d9fe7d7a2 100644 --- a/es-app/src/scrapers/Scraper.cpp +++ b/es-app/src/scrapers/Scraper.cpp @@ -8,6 +8,7 @@ #include "scrapers/Scraper.h" +#include "utils/StringUtil.h" #include "FileData.h" #include "GamesDBJSONScraper.h" #include "ScreenScraper.h" @@ -231,7 +232,12 @@ MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result, if(it->existingMediaFile != "") Utils::FileSystem::removeFile(it->existingMediaFile); + #ifdef _WIN64 + std::ofstream stream(Utils::String::stringToWideString(filePath).c_str(), + std::ios_base::out | std::ios_base::binary); + #else std::ofstream stream(filePath, std::ios_base::out | std::ios_base::binary); + #endif if (stream.bad()) { setError("Failed to open image path to write. Permission error? Disk full?"); return; @@ -331,7 +337,12 @@ void ImageDownloadHandle::update() if(mExistingMediaFile != "") Utils::FileSystem::removeFile(mExistingMediaFile); + #ifdef _WIN64 + std::ofstream stream(Utils::String::stringToWideString(mSavePath).c_str(), + std::ios_base::out | std::ios_base::binary); + #else std::ofstream stream(mSavePath, std::ios_base::out | std::ios_base::binary); + #endif if (stream.bad()) { setError("Failed to open image path to write. Permission error? Disk full?"); return; diff --git a/es-core/src/InputManager.cpp b/es-core/src/InputManager.cpp index 6f18f1c09..947095490 100644 --- a/es-core/src/InputManager.cpp +++ b/es-core/src/InputManager.cpp @@ -9,6 +9,7 @@ #include "InputManager.h" #include "utils/FileSystemUtil.h" +#include "utils/StringUtil.h" #include "CECInput.h" #include "Log.h" #include "Platform.h" @@ -83,7 +84,7 @@ void InputManager::init() loadInputConfig(mKeyboardInputConfig); SDL_USER_CECBUTTONDOWN = SDL_RegisterEvents(2); - SDL_USER_CECBUTTONUP = SDL_USER_CECBUTTONDOWN + 1; + SDL_USER_CECBUTTONUP = SDL_USER_CECBUTTONDOWN + 1; CECInput::init(); mCECInputConfig = new InputConfig(DEVICE_CEC, "CEC", CEC_GUID_STRING); loadInputConfig(mCECInputConfig); @@ -302,7 +303,11 @@ bool InputManager::loadInputConfig(InputConfig* config) return false; pugi::xml_document doc; + #ifdef _WIN64 + pugi::xml_parse_result res = doc.load_file(Utils::String::stringToWideString(path).c_str()); + #else pugi::xml_parse_result res = doc.load_file(path.c_str()); + #endif if (!res) { LOG(LogError) << "Error parsing input config: " << res.description(); @@ -362,7 +367,12 @@ void InputManager::writeDeviceConfig(InputConfig* config) if (Utils::FileSystem::exists(path)) { // Merge files. + #ifdef _WIN64 + pugi::xml_parse_result result = + doc.load_file(Utils::String::stringToWideString(path).c_str()); + #else pugi::xml_parse_result result = doc.load_file(path.c_str()); + #endif if (!result) { LOG(LogError) << "Error parsing input config: " << result.description(); } @@ -400,7 +410,12 @@ void InputManager::writeDeviceConfig(InputConfig* config) root = doc.append_child("inputList"); config->writeToXML(root); + + #ifdef _WIN64 + doc.save_file(Utils::String::stringToWideString(path).c_str()); + #else doc.save_file(path.c_str()); + #endif Scripting::fireEvent("config-changed"); Scripting::fireEvent("controls-changed"); @@ -417,7 +432,13 @@ void InputManager::doOnFinish() pugi::xml_document doc; if (Utils::FileSystem::exists(path)) { + #ifdef _WIN64 + pugi::xml_parse_result result = + doc.load_file(Utils::String::stringToWideString(path).c_str()); + #else pugi::xml_parse_result result = doc.load_file(path.c_str()); + #endif + if (!result) { LOG(LogError) << "Error parsing input config: " << result.description(); } diff --git a/es-core/src/Log.cpp b/es-core/src/Log.cpp index 697c05cdc..cc6183bac 100644 --- a/es-core/src/Log.cpp +++ b/es-core/src/Log.cpp @@ -7,13 +7,15 @@ #include "Log.h" #include "utils/FileSystemUtil.h" +#include "utils/StringUtil.h" #include "Platform.h" #include #include +#include LogLevel Log::reportingLevel = LogInfo; -FILE* Log::file = nullptr; // fopen(getLogPath().c_str(), "w"); +std::ofstream file; LogLevel Log::getReportingLevel() { @@ -32,15 +34,19 @@ void Log::setReportingLevel(LogLevel level) void Log::init() { - remove((getLogPath() + ".bak").c_str()); - // Rename previous log file. - rename(getLogPath().c_str(), (getLogPath() + ".bak").c_str()); + Utils::FileSystem::removeFile(getLogPath() + ".bak"); + // Rename the previous log file. + Utils::FileSystem::renameFile(getLogPath(), getLogPath() + ".bak", true); return; } void Log::open() { - file = fopen(getLogPath().c_str(), "w"); + #ifdef _WIN64 + file.open(Utils::String::stringToWideString(getLogPath()).c_str()); + #else + file.open(getLogPath().c_str()); + #endif } std::ostringstream& Log::get(LogLevel level) @@ -54,36 +60,30 @@ std::ostringstream& Log::get(LogLevel level) void Log::flush() { - fflush(getOutput()); + file.flush(); } void Log::close() { - fclose(file); - file = nullptr; -} - -FILE* Log::getOutput() -{ - return file; + file.close(); } Log::~Log() { os << std::endl; - if (getOutput() == nullptr) { - // not open yet, print to stdout + if (!file.is_open()) { + // Not open yet, print to stdout. std::cerr << "ERROR - tried to write to log file before it was open! " "The following won't be logged:\n"; std::cerr << os.str(); return; } - fprintf(getOutput(), "%s", os.str().c_str()); + file << os.str(); // If it's an error, also print to console. // Print all messages if using --debug. if (messageLevel == LogError || reportingLevel >= LogDebug) - fprintf(stderr, "%s", os.str().c_str()); + std::cerr << os.str(); } diff --git a/es-core/src/Log.h b/es-core/src/Log.h index 9c6f49ad4..810d73340 100644 --- a/es-core/src/Log.h +++ b/es-core/src/Log.h @@ -24,7 +24,6 @@ enum LogLevel { class Log { public: - //Log(); ~Log(); std::ostringstream& get(LogLevel level = LogInfo); @@ -40,11 +39,9 @@ public: protected: std::ostringstream os; - static FILE* file; private: static LogLevel reportingLevel; - static FILE* getOutput(); LogLevel messageLevel; }; diff --git a/es-core/src/MameNames.cpp b/es-core/src/MameNames.cpp index 80561d3c6..9029ceabc 100644 --- a/es-core/src/MameNames.cpp +++ b/es-core/src/MameNames.cpp @@ -50,7 +50,12 @@ MameNames::MameNames() LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"..."; pugi::xml_document doc; + #ifdef _WIN64 + pugi::xml_parse_result result = + doc.load_file(Utils::String::stringToWideString(xmlpath).c_str()); + #else pugi::xml_parse_result result = doc.load_file(xmlpath.c_str()); + #endif if (!result) { LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " @@ -75,7 +80,11 @@ MameNames::MameNames() LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"..."; + #ifdef _WIN64 + result = doc.load_file(Utils::String::stringToWideString(xmlpath).c_str()); + #else result = doc.load_file(xmlpath.c_str()); + #endif if (!result) { LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " @@ -97,7 +106,11 @@ MameNames::MameNames() LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\"..."; + #ifdef _WIN64 + result = doc.load_file(Utils::String::stringToWideString(xmlpath).c_str()); + #else result = doc.load_file(xmlpath.c_str()); + #endif if (!result) { LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " diff --git a/es-core/src/Platform.cpp b/es-core/src/Platform.cpp index be4858e1f..e117e6a65 100644 --- a/es-core/src/Platform.cpp +++ b/es-core/src/Platform.cpp @@ -50,7 +50,7 @@ int runSystemCommand(const std::string& cmd_utf8) #ifdef _WIN64 // On Windows we use _wsystem to support non-ASCII paths // which requires converting from UTF-8 to a wstring. - std::wstring wchar_str = Utils::String::charToWideChar(cmd_utf8); + std::wstring wchar_str = Utils::String::stringToWideString(cmd_utf8); return _wsystem(wchar_str.c_str()); #else return system(cmd_utf8.c_str()); @@ -111,7 +111,7 @@ int launchEmulatorWindows(const std::wstring& cmd_utf16) errorCode = GetLastError(); - std::string errorMessage = Utils::String::wideCharToChar(pBuffer); + std::string errorMessage = Utils::String::wideStringToString(pBuffer); // Remove trailing newline from the error message. if (errorMessage.back() == '\n'); errorMessage.pop_back(); diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index f415e0eb7..10d643855 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -8,6 +8,7 @@ #include "Settings.h" #include "utils/FileSystemUtil.h" +#include "utils/StringUtil.h" #include "Log.h" #include "Scripting.h" #include "Platform.h" @@ -268,7 +269,11 @@ void Settings::saveFile() node.append_attribute("value").set_value(iter->second.c_str()); } + #ifdef _WIN64 + doc.save_file(Utils::String::stringToWideString(path).c_str()); + #else doc.save_file(path.c_str()); + #endif Scripting::fireEvent("config-changed"); Scripting::fireEvent("settings-changed"); @@ -283,7 +288,11 @@ void Settings::loadFile() return; pugi::xml_document doc; + #ifdef _WIN64 + pugi::xml_parse_result result = doc.load_file(Utils::String::stringToWideString(path).c_str()); + #else pugi::xml_parse_result result = doc.load_file(path.c_str()); + #endif if (!result) { LOG(LogError) << "Error - Could not parse Settings file!\n " << result.description(); return; diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index b61d447f8..3bde83b0b 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -260,7 +260,11 @@ void ThemeData::loadFile(std::map sysDataMap, const st mVariables.insert(sysDataMap.cbegin(), sysDataMap.cend()); pugi::xml_document doc; + #ifdef _WIN64 + pugi::xml_parse_result res = doc.load_file(Utils::String::stringToWideString(path).c_str()); + #else pugi::xml_parse_result res = doc.load_file(path.c_str()); + #endif if (!res) throw error << "XML parsing error: \n " << res.description(); @@ -302,7 +306,12 @@ void ThemeData::parseIncludes(const pugi::xml_node& root) mPaths.push_back(path); pugi::xml_document includeDoc; + #ifdef _WIN64 + pugi::xml_parse_result result = + includeDoc.load_file(Utils::String::stringToWideString(path).c_str()); + #else pugi::xml_parse_result result = includeDoc.load_file(path.c_str()); + #endif if (!result) throw error << "Error parsing file: \n " << result.description(); diff --git a/es-core/src/resources/ResourceManager.cpp b/es-core/src/resources/ResourceManager.cpp index 020e1250c..7c0f23e47 100644 --- a/es-core/src/resources/ResourceManager.cpp +++ b/es-core/src/resources/ResourceManager.cpp @@ -8,6 +8,7 @@ #include "ResourceManager.h" #include "utils/FileSystemUtil.h" +#include "utils/StringUtil.h" #include "Log.h" #include "Platform.h" #include "Scripting.h" @@ -88,7 +89,11 @@ const ResourceData ResourceManager::getFileData(const std::string& path) const ResourceData ResourceManager::loadFile(const std::string& path) const { + #ifdef _WIN64 + std::ifstream stream(Utils::String::stringToWideString(path).c_str(), std::ios::binary); + #else std::ifstream stream(path, std::ios::binary); + #endif stream.seekg(0, stream.end); size_t size = (size_t)stream.tellg(); diff --git a/es-core/src/utils/FileSystemUtil.cpp b/es-core/src/utils/FileSystemUtil.cpp index c6e21325e..c51ff22d9 100644 --- a/es-core/src/utils/FileSystemUtil.cpp +++ b/es-core/src/utils/FileSystemUtil.cpp @@ -9,6 +9,8 @@ #define _FILE_OFFSET_BITS 64 #include "utils/FileSystemUtil.h" + +#include "utils/StringUtil.h" #include "Log.h" #include @@ -16,14 +18,8 @@ #include #if defined(_WIN64) -// Because windows... #include #include -#define getcwd _getcwd -#define mkdir(x,y) _mkdir(x) -#define snprintf _snprintf -#define stat64 _stat64 -#define unlink _unlink //#define S_ISREG(x) (((x) & S_IFMT) == S_IFREG) //#define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR) #else @@ -49,21 +45,6 @@ namespace Utils static std::string homePath = ""; static std::string exePath = ""; - #if defined(_WIN64) - static std::string convertFromWideString(const std::wstring wstring) - { - int numBytes = WideCharToMultiByte(CP_UTF8, 0, wstring.c_str(), - (int)wstring.length(), nullptr, 0, nullptr, nullptr); - std::string string; - - string.resize(numBytes); - WideCharToMultiByte(CP_UTF8, 0, wstring.c_str(), (int)wstring.length(), - (char*)string.c_str(), numBytes, nullptr, nullptr); - - return std::string(string); - } - #endif - stringList getDirContent(const std::string& _path, const bool _recursive) { std::string path = getGenericPath(_path); @@ -74,14 +55,13 @@ namespace Utils #if defined(_WIN64) WIN32_FIND_DATAW findData; - std::string wildcard = path + "/*"; - HANDLE hFind = FindFirstFileW(std::wstring(wildcard.begin(), - wildcard.end()).c_str(), &findData); + std::wstring wildcard = Utils::String::stringToWideString(path) + L"/*"; + HANDLE hFind = FindFirstFileW(wildcard.c_str(), &findData); if (hFind != INVALID_HANDLE_VALUE) { // Loop over all files in the directory. do { - std::string name = convertFromWideString(findData.cFileName); + std::string name = Utils::String::wideStringToString(findData.cFileName); // Ignore "." and ".." if ((name != ".") && (name != "..")) { std::string fullName(getGenericPath(path + "/" + name)); @@ -154,8 +134,15 @@ namespace Utils #if defined(_WIN64) // On Windows we need to check HOMEDRIVE and HOMEPATH. if (!homePath.length()) { + #ifdef _WIN64 + std::string envHomeDrive = + Utils::String::wideStringToString(_wgetenv(L"HOMEDRIVE")); + std::string envHomePath = + Utils::String::wideStringToString(_wgetenv(L"HOMEPATH")); + #else std::string envHomeDrive = getenv("HOMEDRIVE"); std::string envHomePath = getenv("HOMEPATH"); + #endif if (envHomeDrive.length() && envHomePath.length()) homePath = getGenericPath(envHomeDrive + "/" + envHomePath); } @@ -180,7 +167,13 @@ namespace Utils char temp[512]; // Return current working directory. + #ifdef _WIN64 + wchar_t tempWide[512]; + return (_wgetcwd(tempWide, 512) ? + getGenericPath(Utils::String::wideStringToString(tempWide)) : ""); + #else return (getcwd(temp, 512) ? getGenericPath(temp) : ""); + #endif } void setExePath(const std::string& _path) @@ -189,7 +182,7 @@ namespace Utils #if defined(_WIN64) std::wstring result(path_max, 0); if (GetModuleFileNameW(nullptr, &result[0], path_max) != 0) - exePath = convertFromWideString(result); + exePath = Utils::String::wideStringToString(result); #else std::string result(path_max, 0); if (readlink("/proc/self/exe", &result[0], path_max) != -1) @@ -539,7 +532,12 @@ namespace Utils return true; } + #ifdef _WIN64 + std::ifstream sourceFile(Utils::String::stringToWideString(_source_path).c_str(), + std::ios::binary); + #else std::ifstream sourceFile(_source_path, std::ios::binary); + #endif if (sourceFile.fail()) { LOG(LogError) << "Error - Couldn't read from source file (" << _source_path << @@ -548,7 +546,12 @@ namespace Utils return true; } + #ifdef _WIN64 + std::ofstream targetFile(Utils::String::stringToWideString(_destination_path).c_str(), + std::ios::binary); + #else std::ofstream targetFile(_destination_path, std::ios::binary); + #endif if (targetFile.fail()) { LOG(LogError) << "Error - Couldn't write to target file (" << _destination_path << @@ -565,6 +568,38 @@ namespace Utils return false; } + bool renameFile(const std::string& _source_path, + const std::string& _destination_path, bool _overwrite) + { + // Don't print any error message for a missing source file as Log will use this + // function when initializing the logging. It would always generate an error in + // case it's the first application start (as an old log file would then not exist). + if (!exists(_source_path)) { + return true; + } + + if(isDirectory(_destination_path)) { + LOG(LogError) << "Error - Destination file is actually a directory:"; + LOG(LogError) << _destination_path; + return true; + } + + if (!_overwrite && exists(_destination_path)) { + LOG(LogError) << "Error - Destination file exists and the overwrite flag " + "has not been set."; + return true; + } + + #ifdef _WIN64 + _wrename(Utils::String::stringToWideString(_source_path).c_str(), + Utils::String::stringToWideString(_destination_path).c_str()); + #else + std::rename(_source_path.c_str(), _destination_path.c_str()); + #endif + + return false; + } + bool removeFile(const std::string& _path) { std::string path = getGenericPath(_path); @@ -574,7 +609,26 @@ namespace Utils return true; // Try to remove file. + #ifdef _WIN64 + if (_wunlink(Utils::String::stringToWideString(path).c_str()) != 0) { + LOG(LogError) << "Error - Couldn't delete file, permission problems?"; + LOG(LogError) << path; + return true; + } + else { + return false; + } + #else + if (unlink(path.c_str()) != 0) { + LOG(LogError) << "Error - Couldn't delete file, permission problems?"; + LOG(LogError) << path; + return true; + } + else { + return false; + } return (unlink(path.c_str()) == 0); + #endif } bool createDirectory(const std::string& _path) @@ -586,8 +640,13 @@ namespace Utils return true; // Try to create directory. + #ifdef _WIN64 + if (_wmkdir(Utils::String::stringToWideString(path).c_str()) == 0) + return true; + #else if (mkdir(path.c_str(), 0755) == 0) return true; + #endif // Failed to create directory, try to create the parent. std::string parent = getParent(path); @@ -597,15 +656,24 @@ namespace Utils createDirectory(parent); // Try to create directory again now that the parent should exist. + #ifdef _WIN64 + return (_wmkdir(Utils::String::stringToWideString(path).c_str()) == 0); + #else return (mkdir(path.c_str(), 0755) == 0); + #endif } bool exists(const std::string& _path) { std::string path = getGenericPath(_path); - struct stat64 info; + #ifdef _WIN64 + struct _stat64 info; + return (_wstat64(Utils::String::stringToWideString(path).c_str(), &info) == 0); + #else + struct stat64 info; return (stat64(path.c_str(), &info) == 0); + #endif } bool isAbsolute(const std::string& _path) @@ -624,8 +692,13 @@ namespace Utils std::string path = getGenericPath(_path); struct stat64 info; + #ifdef _WIN64 + if (_wstat64(Utils::String::stringToWideString(path).c_str(), &info) != 0) + return false; + #else if (stat64(path.c_str(), &info) != 0) return false; + #endif // Check for S_IFREG attribute. return (S_ISREG(info.st_mode)); @@ -634,10 +707,15 @@ namespace Utils bool isDirectory(const std::string& _path) { std::string path = getGenericPath(_path); - struct stat info; + struct stat64 info; - if (stat(path.c_str(), &info) != 0) + #ifdef _WIN64 + if (_wstat64(Utils::String::stringToWideString(path).c_str(), &info) != 0) return false; + #else + if (stat64(path.c_str(), &info) != 0) + return false; + #endif // Check for S_IFDIR attribute. return (S_ISDIR(info.st_mode)); diff --git a/es-core/src/utils/FileSystemUtil.h b/es-core/src/utils/FileSystemUtil.h index 548683958..3958e2e16 100644 --- a/es-core/src/utils/FileSystemUtil.h +++ b/es-core/src/utils/FileSystemUtil.h @@ -47,7 +47,10 @@ namespace Utils std::string resolveSymlink(const std::string& _path); bool copyFile(const std::string& _source_path, const std::string& _destination_path, bool _overwrite); + bool renameFile(const std::string& _source_path, + const std::string& _destination_path, bool _overwrite); bool removeFile(const std::string& _path); + bool createDirectory(const std::string& _path); bool exists(const std::string& _path); bool isAbsolute(const std::string& _path); diff --git a/es-core/src/utils/StringUtil.cpp b/es-core/src/utils/StringUtil.cpp index 530be6ed5..3f1db8c14 100644 --- a/es-core/src/utils/StringUtil.cpp +++ b/es-core/src/utils/StringUtil.cpp @@ -187,7 +187,7 @@ namespace Utils return string; } - std::wstring charToWideChar(const std::string& _string) + std::wstring stringToWideString(const std::string& _string) { typedef std::codecvt_utf8 convert_type; std::wstring_convert stringConverter; @@ -195,7 +195,7 @@ namespace Utils return stringConverter.from_bytes(_string); } - std::string wideCharToChar(const std::wstring& _string) + std::string wideStringToString(const std::wstring& _string) { typedef std::codecvt_utf8 convert_type; std::wstring_convert stringConverter; diff --git a/es-core/src/utils/StringUtil.h b/es-core/src/utils/StringUtil.h index 5cb423268..da8d5e508 100644 --- a/es-core/src/utils/StringUtil.h +++ b/es-core/src/utils/StringUtil.h @@ -28,8 +28,8 @@ namespace Utils std::string trim(const std::string& _string); std::string replace(const std::string& _string, const std::string& _replace, const std::string& _with); - std::wstring charToWideChar(const std::string& _string); - std::string wideCharToChar(const std::wstring& _string); + std::wstring stringToWideString(const std::string& _string); + std::string wideStringToString(const std::wstring& _string); bool startsWith(const std::string& _string, const std::string& _start); bool endsWith(const std::string& _string, const std::string& _end); std::string removeParenthesis(const std::string& _string);