diff --git a/CHANGELOG.md b/CHANGELOG.md index ec7da77798..ab2c09b38c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -120,6 +120,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - New `GameActivity::LockControlledActor` Lua function to allow grab player input in the way menus (buy menu/full inventorymenu) do. +- New `LuaMan` Lua I/O functions `DirectoryCreate`, `FileExists`, `DirectoryExists`, `FileRemove`, `DirectoryRemove`, `FileRename`, `DirectoryRename` and `IsValidModulePath`. + - New `FrameMan` Lua functions `SetHudDisabled(disabled, screenId)` and `IsHudDisabled(screenId)` that allows disabling a given screen's HUD, and checking whether it's currently disabled. The screenId parameters are optional and default to screen 0. - Exposed `FrameMan` property `ScreenCount` to Lua (R). diff --git a/Source/Managers/LuaMan.cpp b/Source/Managers/LuaMan.cpp index c6443f6e81..1395d48169 100644 --- a/Source/Managers/LuaMan.cpp +++ b/Source/Managers/LuaMan.cpp @@ -71,8 +71,17 @@ namespace RTE { .def("GetDirectoryList", &LuaStateWrapper::DirectoryList, luabind::adopt(luabind::return_value) + luabind::return_stl_iterator) .def("GetFileList", &LuaStateWrapper::FileList, luabind::adopt(luabind::return_value) + luabind::return_stl_iterator) .def("FileExists", &LuaStateWrapper::FileExists) + .def("DirectoryExists", &LuaStateWrapper::DirectoryExists) + .def("IsValidModulePath", &LuaStateWrapper::IsValidModulePath) .def("FileOpen", &LuaStateWrapper::FileOpen) .def("FileClose", &LuaStateWrapper::FileClose) + .def("FileRemove", &LuaStateWrapper::FileRemove) + .def("DirectoryCreate", &LuaStateWrapper::DirectoryCreate1) + .def("DirectoryCreate", &LuaStateWrapper::DirectoryCreate2) + .def("DirectoryRemove", &LuaStateWrapper::DirectoryRemove1) + .def("DirectoryRemove", &LuaStateWrapper::DirectoryRemove2) + .def("FileRename", &LuaStateWrapper::FileRename) + .def("DirectoryRename", &LuaStateWrapper::DirectoryRename) .def("FileReadLine", &LuaStateWrapper::FileReadLine) .def("FileWriteLine", &LuaStateWrapper::FileWriteLine) .def("FileEOF", &LuaStateWrapper::FileEOF), @@ -273,12 +282,21 @@ namespace RTE { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Passthrough LuaMan Functions - const std::vector* LuaStateWrapper::DirectoryList(const std::string& relativeDirectory) { return g_LuaMan.DirectoryList(relativeDirectory); } - const std::vector* LuaStateWrapper::FileList(const std::string& relativeDirectory) { return g_LuaMan.FileList(relativeDirectory); } - bool LuaStateWrapper::FileExists(const std::string &fileName) { return g_LuaMan.FileExists(fileName); } - int LuaStateWrapper::FileOpen(const std::string& fileName, const std::string& accessMode) { return g_LuaMan.FileOpen(fileName, accessMode); } + const std::vector* LuaStateWrapper::DirectoryList(const std::string& path) { return g_LuaMan.DirectoryList(path); } + const std::vector* LuaStateWrapper::FileList(const std::string& path) { return g_LuaMan.FileList(path); } + bool LuaStateWrapper::FileExists(const std::string &path) { return g_LuaMan.FileExists(path); } + bool LuaStateWrapper::DirectoryExists(const std::string &path) { return g_LuaMan.DirectoryExists(path); } + bool LuaStateWrapper::IsValidModulePath(const std::string &path) { return g_LuaMan.IsValidModulePath(path); } + int LuaStateWrapper::FileOpen(const std::string& path, const std::string& accessMode) { return g_LuaMan.FileOpen(path, accessMode); } void LuaStateWrapper::FileClose(int fileIndex) { return g_LuaMan.FileClose(fileIndex); } void LuaStateWrapper::FileCloseAll() { return g_LuaMan.FileCloseAll(); } + bool LuaStateWrapper::FileRemove(const std::string& path) { return g_LuaMan.FileRemove(path); } + bool LuaStateWrapper::DirectoryCreate1(const std::string& path) { return g_LuaMan.DirectoryCreate(path, false); } + bool LuaStateWrapper::DirectoryCreate2(const std::string& path, bool recursive) { return g_LuaMan.DirectoryCreate(path, recursive); } + bool LuaStateWrapper::DirectoryRemove1(const std::string& path) { return g_LuaMan.DirectoryRemove(path, false); } + bool LuaStateWrapper::DirectoryRemove2(const std::string& path, bool recursive) { return g_LuaMan.DirectoryRemove(path, recursive); } + bool LuaStateWrapper::FileRename(const std::string& oldPath, const std::string& newPath) { return g_LuaMan.FileRename(oldPath, newPath); } + bool LuaStateWrapper::DirectoryRename(const std::string& oldPath, const std::string& newPath) { return g_LuaMan.DirectoryRename(oldPath, newPath); } std::string LuaStateWrapper::FileReadLine(int fileIndex) { return g_LuaMan.FileReadLine(fileIndex); } void LuaStateWrapper::FileWriteLine(int fileIndex, const std::string& line) { return g_LuaMan.FileWriteLine(fileIndex, line); } bool LuaStateWrapper::FileEOF(int fileIndex) { return g_LuaMan.FileEOF(fileIndex); } @@ -349,7 +367,7 @@ namespace RTE { // TODO // It would be nice to assign to least-saturated state, but that's a bit tricky with MO registering... - /*auto itr = std::min_element(m_ScriptStates.begin(), m_ScriptStates.end(), + /*auto itr = std::min_element(m_ScriptStates.begin(), m_ScriptStates.end(), [](const LuaStateWrapper& lhs, const LuaStateWrapper& rhs) { return lhs.GetRegisteredMOs().size() < rhs.GetRegisteredMOs().size(); } ); @@ -364,7 +382,7 @@ namespace RTE { bool success = m_ScriptStates[ourState].GetMutex().try_lock(); RTEAssert(success, "Script mutex was already locked while in a non-multithreaded environment!"); - return &m_ScriptStates[ourState]; + return &m_ScriptStates[ourState]; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -394,13 +412,13 @@ namespace RTE { void LuaMan::ExecuteLuaScriptCallbacks() { std::vector> callbacks; - + // Move our functions into the local buffer to clear the existing callbacks and to lock for as little time as possible { std::scoped_lock lock(m_ScriptCallbacksMutex); callbacks.swap(m_ScriptCallbacks); } - + for (const std::function &callback : callbacks) { callback(); } @@ -860,7 +878,7 @@ namespace RTE { bool LuaStateWrapper::TableEntryIsDefined(const std::string &tableName, const std::string &indexName) { std::lock_guard lock(m_Mutex); - + // Push the table onto the stack, checking if it even exists. lua_getglobal(m_State, tableName.c_str()); if (!lua_istable(m_State, -1)) { @@ -928,40 +946,80 @@ namespace RTE { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - const std::vector * LuaMan::DirectoryList(const std::string &filePath) { + const std::vector * LuaMan::DirectoryList(const std::string &path) { + std::string fullPath = System::GetWorkingDirectory() + g_PresetMan.GetFullModulePath(path); auto *directoryPaths = new std::vector(); - for (const std::filesystem::directory_entry &directoryEntry : std::filesystem::directory_iterator(System::GetWorkingDirectory() + filePath)) { - if (directoryEntry.is_directory()) { directoryPaths->emplace_back(directoryEntry.path().filename().generic_string()); } + if (IsValidModulePath(fullPath)) { +#ifndef _WIN32 + fullPath = GetCaseInsensitiveFullPath(fullPath); +#endif + if (std::filesystem::exists(fullPath)) + { + for (const std::filesystem::directory_entry &directoryEntry : std::filesystem::directory_iterator(fullPath)) { + if (directoryEntry.is_directory()) { directoryPaths->emplace_back(directoryEntry.path().filename().generic_string()); } + } + } } return directoryPaths; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - const std::vector * LuaMan::FileList(const std::string &filePath) { + const std::vector * LuaMan::FileList(const std::string &path) { + std::string fullPath = System::GetWorkingDirectory() + g_PresetMan.GetFullModulePath(path); auto *filePaths = new std::vector(); - for (const std::filesystem::directory_entry &directoryEntry : std::filesystem::directory_iterator(System::GetWorkingDirectory() + filePath)) { - if (directoryEntry.is_regular_file()) { filePaths->emplace_back(directoryEntry.path().filename().generic_string()); } + if (IsValidModulePath(fullPath)) { +#ifndef _WIN32 + fullPath = GetCaseInsensitiveFullPath(fullPath); +#endif + if (std::filesystem::exists(fullPath)) + { + for (const std::filesystem::directory_entry &directoryEntry : std::filesystem::directory_iterator(fullPath)) { + if (directoryEntry.is_regular_file()) { filePaths->emplace_back(directoryEntry.path().filename().generic_string()); } + } + } } return filePaths; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - bool LuaMan::FileExists(const std::string &fileName) { - std::string fullPath = System::GetWorkingDirectory() + g_PresetMan.GetFullModulePath(fileName); - if ((fullPath.find("..") == std::string::npos) && (fullPath.find(System::GetModulePackageExtension()) != std::string::npos)) { - return std::filesystem::exists(fullPath); + bool LuaMan::FileExists(const std::string &path) { + std::string fullPath = System::GetWorkingDirectory() + g_PresetMan.GetFullModulePath(path); + if (IsValidModulePath(fullPath)) { +#ifndef _WIN32 + fullPath = GetCaseInsensitiveFullPath(fullPath); +#endif + return std::filesystem::is_regular_file(fullPath); } + return false; + } + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + bool LuaMan::DirectoryExists(const std::string &path) { + std::string fullPath = System::GetWorkingDirectory() + g_PresetMan.GetFullModulePath(path); + if (IsValidModulePath(fullPath)) { +#ifndef _WIN32 + fullPath = GetCaseInsensitiveFullPath(fullPath); +#endif + return std::filesystem::is_directory(fullPath); + } return false; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - int LuaMan::FileOpen(const std::string &fileName, const std::string &accessMode) { + // TODO: Move to ModuleMan, once the ModuleMan PR has been merged + bool LuaMan::IsValidModulePath(const std::string &path) { + return (path.find("..") == std::string::npos) && (path.find(System::GetModulePackageExtension()) != std::string::npos); + } + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + int LuaMan::FileOpen(const std::string &path, const std::string &accessMode) { if (c_FileAccessModes.find(accessMode) == c_FileAccessModes.end()) { g_ConsoleMan.PrintString("ERROR: Cannot open file, invalid file access mode specified."); return -1; @@ -979,9 +1037,8 @@ namespace RTE { return -1; } - std::string fullPath = System::GetWorkingDirectory() + g_PresetMan.GetFullModulePath(fileName); - if ((fullPath.find("..") == std::string::npos) && (fullPath.find(System::GetModulePackageExtension()) != std::string::npos)) { - + std::string fullPath = System::GetWorkingDirectory() + g_PresetMan.GetFullModulePath(path); + if (IsValidModulePath(fullPath)) { #ifdef _WIN32 FILE *file = fopen(fullPath.c_str(), accessMode.c_str()); #else @@ -989,27 +1046,34 @@ namespace RTE { std::filesystem::path inspectedPath = System::GetWorkingDirectory(); const std::filesystem::path relativeFilePath = std::filesystem::path(fullPath).lexically_relative(inspectedPath); + // Iterate over all path parts for (std::filesystem::path::const_iterator relativeFilePathIterator = relativeFilePath.begin(); relativeFilePathIterator != relativeFilePath.end(); ++relativeFilePathIterator) { bool pathPartExists = false; - // Check if a path part (directory or file) exists in the filesystem. + // Iterate over all entries in the path part's directory, + // to check if the path part is in there case insensitively for (const std::filesystem::path &filesystemEntryPath : std::filesystem::directory_iterator(inspectedPath)) { - if (StringsEqualCaseInsensitive(filesystemEntryPath.filename().generic_string(), (*relativeFilePathIterator).generic_string())) { + if (StringsEqualCaseInsensitive(filesystemEntryPath.filename().generic_string(), relativeFilePathIterator->generic_string())) { inspectedPath = filesystemEntryPath; + + // If the path part is found, stop looking for it pathPartExists = true; break; } } + if (!pathPartExists) { - // If this is the last part, then all directories in relativeFilePath exist, but the file doesn't. + // If this is the last part, then all directories in relativeFilePath exist, but the file doesn't if (std::next(relativeFilePathIterator) == relativeFilePath.end()) { return fopen((inspectedPath / relativeFilePath.filename()).generic_string().c_str(), accessMode.c_str()); } - // Some directory in relativeFilePath doesn't exist, so the file can't be created. + + // Some directory in relativeFilePath doesn't exist, so the file can't be created return nullptr; } } - // If the file exists, open it. + + // If the file exists, open it return fopen(inspectedPath.generic_string().c_str(), accessMode.c_str()); }(); #endif @@ -1018,7 +1082,7 @@ namespace RTE { return fileIndex; } } - g_ConsoleMan.PrintString("ERROR: Failed to open file " + fileName); + g_ConsoleMan.PrintString("ERROR: Failed to open file " + path); return -1; } @@ -1039,6 +1103,112 @@ namespace RTE { } } +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + bool LuaMan::FileRemove(const std::string& path) { + std::string fullPath = System::GetWorkingDirectory() + g_PresetMan.GetFullModulePath(path); + if (IsValidModulePath(fullPath)) { +#ifndef _WIN32 + fullPath = GetCaseInsensitiveFullPath(fullPath); +#endif + if (std::filesystem::is_regular_file(fullPath)) { + return std::filesystem::remove(fullPath); + } + } + g_ConsoleMan.PrintString("ERROR: Failed to remove file " + path); + return false; + } + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + bool LuaMan::DirectoryCreate(const std::string& path, bool recursive) { + std::string fullPath = System::GetWorkingDirectory() + g_PresetMan.GetFullModulePath(path); + if (IsValidModulePath(fullPath)) { +#ifndef _WIN32 + fullPath = GetCaseInsensitiveFullPath(fullPath); +#endif + try { + if (recursive) { + return std::filesystem::create_directories(fullPath); + } else { + return std::filesystem::create_directory(fullPath); + } + } catch (const std::filesystem::filesystem_error &e) {} + } + g_ConsoleMan.PrintString("ERROR: Failed to remove directory " + path); + return false; + } + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + bool LuaMan::DirectoryRemove(const std::string& path, bool recursive) { + std::string fullPath = System::GetWorkingDirectory() + g_PresetMan.GetFullModulePath(path); + if (IsValidModulePath(fullPath)) { +#ifndef _WIN32 + fullPath = GetCaseInsensitiveFullPath(fullPath); +#endif + if (std::filesystem::is_directory(fullPath)) { + try { + if (recursive) { + return std::filesystem::remove_all(fullPath) > 0; + } else { + return std::filesystem::remove(fullPath); + } + } catch (const std::filesystem::filesystem_error &e) {} + } + } + g_ConsoleMan.PrintString("ERROR: Failed to remove directory " + path); + return false; + } + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + bool LuaMan::FileRename(const std::string& oldPath, const std::string& newPath) { + std::string fullOldPath = System::GetWorkingDirectory() + g_PresetMan.GetFullModulePath(oldPath); + std::string fullNewPath = System::GetWorkingDirectory() + g_PresetMan.GetFullModulePath(newPath); + if (IsValidModulePath(fullOldPath) && IsValidModulePath(fullNewPath)) { +#ifndef _WIN32 + fullOldPath = GetCaseInsensitiveFullPath(fullOldPath); + fullNewPath = GetCaseInsensitiveFullPath(fullNewPath); +#endif + // Ensures parity between Linux which can overwrite an empty directory, while Windows can't + // Ensures parity between Linux which can't rename a directory to a newPath that is a file in order to overwrite it, while Windows can + if (std::filesystem::is_regular_file(fullOldPath) && !std::filesystem::exists(fullNewPath)) + { + try { + std::filesystem::rename(fullOldPath, fullNewPath); + return true; + } catch (const std::filesystem::filesystem_error &e) {} + } + } + g_ConsoleMan.PrintString("ERROR: Failed to rename oldPath " + oldPath + " to newPath " + newPath); + return false; + } + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + bool LuaMan::DirectoryRename(const std::string& oldPath, const std::string& newPath) { + std::string fullOldPath = System::GetWorkingDirectory() + g_PresetMan.GetFullModulePath(oldPath); + std::string fullNewPath = System::GetWorkingDirectory() + g_PresetMan.GetFullModulePath(newPath); + if (IsValidModulePath(fullOldPath) && IsValidModulePath(fullNewPath)) { +#ifndef _WIN32 + fullOldPath = GetCaseInsensitiveFullPath(fullOldPath); + fullNewPath = GetCaseInsensitiveFullPath(fullNewPath); +#endif + // Ensures parity between Linux which can overwrite an empty directory, while Windows can't + // Ensures parity between Linux which can't rename a directory to a newPath that is a file in order to overwrite it, while Windows can + if (std::filesystem::is_directory(fullOldPath) && !std::filesystem::exists(fullNewPath)) + { + try { + std::filesystem::rename(fullOldPath, fullNewPath); + return true; + } catch (const std::filesystem::filesystem_error &e) {} + } + } + g_ConsoleMan.PrintString("ERROR: Failed to rename oldPath " + oldPath + " to newPath " + newPath); + return false; + } + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// std::string LuaMan::FileReadLine(int fileIndex) { @@ -1096,7 +1266,7 @@ namespace RTE { void LuaMan::StartAsyncGarbageCollection() { ZoneScoped; - + std::vector allStates; allStates.reserve(m_ScriptStates.size() + 1); @@ -1104,7 +1274,7 @@ namespace RTE { for (LuaStateWrapper& wrapper : m_ScriptStates) { allStates.push_back(&wrapper); } - + m_GarbageCollectionTask = BS::multi_future(); for (LuaStateWrapper* luaState : allStates) { m_GarbageCollectionTask.push_back( diff --git a/Source/Managers/LuaMan.h b/Source/Managers/LuaMan.h index fe78e0935b..d6358c615d 100644 --- a/Source/Managers/LuaMan.h +++ b/Source/Managers/LuaMan.h @@ -82,7 +82,7 @@ namespace RTE { /// /// This LuaStateWrapper's internal lua state. lua_State* GetLuaState() { return m_State; }; - + /// /// Gets m_ScriptTimings. /// @@ -284,12 +284,21 @@ namespace RTE { double PosRand(); #pragma region Passthrough LuaMan Functions - const std::vector* DirectoryList(const std::string& relativeDirectory); - const std::vector* FileList(const std::string& relativeDirectory); - bool FileExists(const std::string &fileName); - int FileOpen(const std::string& fileName, const std::string& accessMode); + const std::vector* DirectoryList(const std::string& path); + const std::vector* FileList(const std::string& path); + bool FileExists(const std::string &path); + bool DirectoryExists(const std::string &path); + bool IsValidModulePath(const std::string &path); + int FileOpen(const std::string& path, const std::string& accessMode); void FileClose(int fileIndex); void FileCloseAll(); + bool FileRemove(const std::string& path); + bool DirectoryCreate1(const std::string& path); + bool DirectoryCreate2(const std::string& path, bool recursive); + bool DirectoryRemove1(const std::string& path); + bool DirectoryRemove2(const std::string& path, bool recursive); + bool FileRename(const std::string& oldPath, const std::string& newPath); + bool DirectoryRename(const std::string& oldPath, const std::string& newPath); std::string FileReadLine(int fileIndex); void FileWriteLine(int fileIndex, const std::string& line); bool FileEOF(int fileIndex); @@ -376,7 +385,7 @@ namespace RTE { /// /// A list of threaded script states. LuaStatesArray & GetThreadedScriptStates(); - + /// /// Gets the current thread lua state override that new objects created will be assigned to. /// @@ -428,36 +437,48 @@ namespace RTE { #pragma region File I/O Handling /// - /// Returns a vector of all the directories in relativeDirectory, which is relative to the working directory. - /// Note that a call to this method overwrites any previously returned vector from DirectoryList() or FileList(). + /// Returns a vector of all the directories in path, which is relative to the working directory. /// - /// Directory path relative to the working directory. - /// A vector of the directories in relativeDirectory. - const std::vector * DirectoryList(const std::string &relativeDirectory); + /// Directory path relative to the working directory. + /// A vector of the directories in path. + const std::vector * DirectoryList(const std::string &path); /// - /// Returns a vector of all the files in relativeDirectory, which is relative to the working directory. - /// Note that a call to this method overwrites any previously returned vector from DirectoryList() or FileList(). + /// Returns a vector of all the files in path, which is relative to the working directory. /// - /// Directory path relative to the working directory. - /// A vector of the files in relativeDirectory. - const std::vector * FileList(const std::string &relativeDirectory); + /// Directory path relative to the working directory. + /// A vector of the files in path. + const std::vector * FileList(const std::string &path); /// /// Returns whether or not the specified file exists. You can only check for files inside .rte folders in the working directory. /// - /// Path to the file. All paths are made absolute by adding current working directory to the specified path. + /// Path to the file. All paths are made absolute by adding current working directory to the specified path. /// Whether or not the specified file exists. - bool FileExists(const std::string &fileName); + bool FileExists(const std::string &path); /// - /// Opens a file or creates one if it does not exist, depending on access mode. You can open files only inside .rte folders in the working directly. You can't open more that c_MaxOpenFiles file simultaneously. + /// Returns whether or not the specified directory exists. You can only check for directories inside .rte folders in the working directory. + /// + /// Path to the directory. All paths are made absolute by adding current working directory to the specified path. + /// Whether or not the specified file exists. + bool DirectoryExists(const std::string &path); + + /// + /// Returns whether or not the path refers to an accessible file or directory. You can only check for files or directories inside .rte directories in the working directory. + /// + /// Path to the file or directory. All paths are made absolute by adding current working directory to the specified path. + /// Whether or not the specified file exists. + bool IsValidModulePath(const std::string &path); + + /// + /// Opens a file or creates one if it does not exist, depending on access mode. You can open files only inside .rte folders in the working directory. You can't open more that c_MaxOpenFiles file simultaneously. /// On Linux will attempt to open a file case insensitively. /// - /// Path to the file. All paths are made absolute by adding current working directory to the specified path. + /// Path to the file. All paths are made absolute by adding current working directory to the specified path. /// File access mode. See 'fopen' for list of modes. /// File index in the opened files array. - int FileOpen(const std::string &fileName, const std::string &accessMode); + int FileOpen(const std::string &path, const std::string &accessMode); /// /// Closes a previously opened file. @@ -470,6 +491,49 @@ namespace RTE { /// void FileCloseAll(); + /// + /// Removes a file. + /// + /// Path to the file. All paths are made absolute by adding current working directory to the specified path. + /// Whether or not the file was removed. + bool FileRemove(const std::string &path); + + /// + /// Creates a directory, optionally recursively. + /// + /// Path to the directory to be created. All paths are made absolute by adding current working directory to the specified path. + /// Whether to recursively create parent directories. + /// Whether or not the directory was removed. + bool DirectoryCreate(const std::string &path, bool recursive); + + /// + /// Removes a directory, optionally recursively. + /// + /// Path to the directory to be removed. All paths are made absolute by adding current working directory to the specified path. + /// Whether to recursively remove files and directories. + /// Whether or not the directory was removed. + bool DirectoryRemove(const std::string &path, bool recursive); + + /// + /// Moves or renames the file oldPath to newPath. + /// In order to get consistent behavior across Windows and Linux across all 4 combinations of oldPath and newPath being a directory/file, + /// the newPath isn't allowed to already exist. + /// + /// Path to the filesystem object. All paths are made absolute by adding current working directory to the specified path. + /// Path to the filesystem object. All paths are made absolute by adding current working directory to the specified path. + /// Whether or not renaming succeeded. + bool FileRename(const std::string &oldPath, const std::string &newPath); + + /// + /// Moves or renames the directory oldPath to newPath. + /// In order to get consistent behavior across Windows and Linux across all 4 combinations of oldPath and newPath being a directory/file, + /// the newPath isn't allowed to already exist. + /// + /// Path to the filesystem object. All paths are made absolute by adding current working directory to the specified path. + /// Path to the filesystem object. All paths are made absolute by adding current working directory to the specified path. + /// Whether or not renaming succeeded. + bool DirectoryRename(const std::string &oldPath, const std::string &newPath); + /// /// Reads a line from a file. /// @@ -536,4 +600,4 @@ namespace RTE { LuaMan & operator=(const LuaMan &rhs) = delete; }; } -#endif \ No newline at end of file +#endif diff --git a/Source/System/RTETools.cpp b/Source/System/RTETools.cpp index 5c1af24ee3..81f36c635c 100644 --- a/Source/System/RTETools.cpp +++ b/Source/System/RTETools.cpp @@ -218,4 +218,39 @@ namespace RTE { return hash; } + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + std::string GetCaseInsensitiveFullPath(const std::string &fullPath) { + std::filesystem::path inspectedPath = System::GetWorkingDirectory(); + const std::filesystem::path relativeFilePath = std::filesystem::path(fullPath).lexically_relative(inspectedPath); + + // Iterate over all path parts + for (std::filesystem::path::const_iterator relativeFilePathIterator = relativeFilePath.begin(); relativeFilePathIterator != relativeFilePath.end(); ++relativeFilePathIterator) { + bool pathPartExists = false; + + // Iterate over all entries in the path part's directory, + // to check if the path part is in there case insensitively + for (const std::filesystem::path &filesystemEntryPath : std::filesystem::directory_iterator(inspectedPath)) { + if (StringsEqualCaseInsensitive(filesystemEntryPath.filename().generic_string(), relativeFilePathIterator->generic_string())) { + inspectedPath = filesystemEntryPath; + + // If the path part is found, stop looking for it + pathPartExists = true; + break; + } + } + + if (!pathPartExists) { + // If part of the path exists, append the rest of fullPath its parts + while (relativeFilePathIterator != relativeFilePath.end()) { + inspectedPath /= relativeFilePathIterator->generic_string(); + relativeFilePathIterator++; + } + break; + } + } + + return inspectedPath.generic_string(); + } } diff --git a/Source/System/RTETools.h b/Source/System/RTETools.h index 9637691d35..b69b4fdcbd 100644 --- a/Source/System/RTETools.h +++ b/Source/System/RTETools.h @@ -292,6 +292,15 @@ namespace RTE { /// Whether the two strings are equal case insensitively. inline bool StringsEqualCaseInsensitive(const std::string_view &strA, const std::string_view &strB) { return std::equal(strA.begin(), strA.end(), strB.begin(), strB.end(), [](char strAChar, char strBChar) { return std::tolower(strAChar) == std::tolower(strBChar); }); } + /// + /// If a file "foo/Bar.txt" exists, and this method is passed "FOO/BAR.TXT", then this method will return "foo/Bar.txt". + /// This method's purpose is to enable Linux to get the real path using a case-insensitive search. + /// The real path is used by the Lua file I/O handling methods to ensure full Windows compatibility. + /// + /// Path to case-insensitively translate to a real path. + /// The real path. If the path doesn't exist, it returns the fullPath argument with all the existing parent directories correctly capitalized. + std::string GetCaseInsensitiveFullPath(const std::string &fullPath); + /// /// Hashes a string in a cross-compiler/platform safe way (std::hash gives different results on different compilers). ///