You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Framework/AzFramework/AzFramework/IO/LocalFileIO.cpp

831 lines
31 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzFramework/IO/LocalFileIO.h>
#include <sys/stat.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/IO/IOUtils.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <cctype>
namespace AZ
{
namespace IO
{
const HandleType LocalHandleStartValue = 1000000; //start the local file io handles at 1 million
LocalFileIO::LocalFileIO()
{
m_nextHandle = LocalHandleStartValue;
}
LocalFileIO::~LocalFileIO()
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_openFileGuard);
while (!m_openFiles.empty())
{
const auto& handlePair = *m_openFiles.begin();
Close(handlePair.first);
}
AZ_Assert(m_openFiles.empty(), "Trying to shutdown filing system with files still open");
}
Result LocalFileIO::Open(const char* filePath, OpenMode mode, HandleType& fileHandle)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
AZ::IO::UpdateOpenModeForReading(mode);
// Generate open modes for SystemFile
int systemFileMode = TranslateOpenModeToSystemFileMode(resolvedPath, mode);
bool write = AnyFlag(mode & (OpenMode::ModeWrite | OpenMode::ModeUpdate | OpenMode::ModeAppend));
if (write)
{
CheckInvalidWrite(resolvedPath);
}
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_openFileGuard);
fileHandle = GetNextHandle();
// Construct a new SystemFile in the map (SystemFiles don't copy/move very well).
auto newPair = m_openFiles.emplace(fileHandle);
// Check for successful insert
if (!newPair.second)
{
fileHandle = InvalidHandle;
return ResultCode::Error;
}
// Attempt to open the newly created file
if (newPair.first->second.Open(resolvedPath, systemFileMode, 0))
{
return ResultCode::Success;
}
else
{
// Remove file, it's not actually open
m_openFiles.erase(fileHandle);
// On failure, ensure the fileHandle returned is invalid
// some code does not check return but handle value (equivalent to checking for nullptr FILE*)
fileHandle = InvalidHandle;
return ResultCode::Error;
}
}
}
Result LocalFileIO::Close(HandleType fileHandle)
{
auto filePointer = GetFilePointerFromHandle(fileHandle);
if (!filePointer)
{
return ResultCode::Error_HandleInvalid;
}
filePointer->Close();
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_openFileGuard);
m_openFiles.erase(fileHandle);
}
return ResultCode::Success;
}
Result LocalFileIO::Read(HandleType fileHandle, void* buffer, AZ::u64 size, bool failOnFewerThanSizeBytesRead, AZ::u64* bytesRead)
{
auto filePointer = GetFilePointerFromHandle(fileHandle);
if (!filePointer)
{
return ResultCode::Error_HandleInvalid;
}
SystemFile::SizeType readResult = filePointer->Read(size, buffer);
if (bytesRead)
{
*bytesRead = aznumeric_cast<AZ::u64>(readResult);
}
if (static_cast<AZ::u64>(readResult) != size)
{
if (failOnFewerThanSizeBytesRead)
{
return ResultCode::Error;
}
// Reading less than desired is valid if ferror is not set
AZ_Assert(Eof(fileHandle), "End of file unexpectedly reached before all data was read");
}
return ResultCode::Success;
}
Result LocalFileIO::Write(HandleType fileHandle, const void* buffer, AZ::u64 size, AZ::u64* bytesWritten)
{
auto filePointer = GetFilePointerFromHandle(fileHandle);
if (!filePointer)
{
return ResultCode::Error_HandleInvalid;
}
SystemFile::SizeType writeResult = filePointer->Write(buffer, size);
if (bytesWritten)
{
*bytesWritten = writeResult;
}
if (static_cast<AZ::u64>(writeResult) != size)
{
return ResultCode::Error;
}
return ResultCode::Success;
}
Result LocalFileIO::Flush(HandleType fileHandle)
{
auto filePointer = GetFilePointerFromHandle(fileHandle);
if (!filePointer)
{
return ResultCode::Error_HandleInvalid;
}
filePointer->Flush();
return ResultCode::Success;
}
Result LocalFileIO::Tell(HandleType fileHandle, AZ::u64& offset)
{
auto filePointer = GetFilePointerFromHandle(fileHandle);
if (!filePointer)
{
return ResultCode::Error_HandleInvalid;
}
SystemFile::SizeType resultValue = filePointer->Tell();
if (resultValue == -1)
{
return ResultCode::Error;
}
offset = static_cast<AZ::u64>(resultValue);
return ResultCode::Success;
}
Result LocalFileIO::Seek(HandleType fileHandle, AZ::s64 offset, SeekType type)
{
auto filePointer = GetFilePointerFromHandle(fileHandle);
if (!filePointer)
{
return ResultCode::Error_HandleInvalid;
}
SystemFile::SeekMode mode = SystemFile::SF_SEEK_BEGIN;
if (type == SeekType::SeekFromCurrent)
{
mode = SystemFile::SF_SEEK_CURRENT;
}
else if (type == SeekType::SeekFromEnd)
{
mode = SystemFile::SF_SEEK_END;
}
filePointer->Seek(offset, mode);
return ResultCode::Success;
}
Result LocalFileIO::Size(HandleType fileHandle, AZ::u64& size)
{
auto filePointer = GetFilePointerFromHandle(fileHandle);
if (!filePointer)
{
return ResultCode::Error_HandleInvalid;
}
size = filePointer->Length();
return ResultCode::Success;
}
Result LocalFileIO::Size(const char* filePath, AZ::u64& size)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
size = SystemFile::Length(resolvedPath);
if (!size)
{
return SystemFile::Exists(resolvedPath) ? ResultCode::Success : ResultCode::Error;
}
return ResultCode::Success;
}
bool LocalFileIO::IsReadOnly(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
return !SystemFile::IsWritable(resolvedPath);
}
bool LocalFileIO::Eof(HandleType fileHandle)
{
auto filePointer = GetFilePointerFromHandle(fileHandle);
if (!filePointer)
{
return false;
}
return filePointer->Eof();
}
AZ::u64 LocalFileIO::ModificationTime(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
return SystemFile::ModificationTime(resolvedPath);
}
AZ::u64 LocalFileIO::ModificationTime(HandleType fileHandle)
{
auto filePointer = GetFilePointerFromHandle(fileHandle);
if (!filePointer)
{
return 0;
}
return filePointer->ModificationTime();
}
bool LocalFileIO::Exists(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
return SystemFile::Exists(resolvedPath);
}
void LocalFileIO::CheckInvalidWrite([[maybe_unused]] const char* path)
{
#if defined(AZ_ENABLE_TRACING)
const char* assetsAlias = GetAlias("@assets@");
if (path && assetsAlias && AZ::IO::PathView(path).IsRelativeTo(assetsAlias))
{
AZ_Error("FileIO", false, "You may not alter data inside the asset cache. Please check the call stack and consider writing into the source asset folder instead.\n"
"Attempted write location: %s", path);
}
#endif
}
Result LocalFileIO::Remove(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
CheckInvalidWrite(resolvedPath);
if (IsDirectory(resolvedPath))
{
return ResultCode::Error;
}
return SystemFile::Delete(resolvedPath) ? ResultCode::Success : ResultCode::Error;
}
Result LocalFileIO::Rename(const char* originalFilePath, const char* newFilePath)
{
char resolvedOldPath[AZ_MAX_PATH_LEN];
char resolvedNewPath[AZ_MAX_PATH_LEN];
ResolvePath(originalFilePath, resolvedOldPath, AZ_MAX_PATH_LEN);
ResolvePath(newFilePath, resolvedNewPath, AZ_MAX_PATH_LEN);
CheckInvalidWrite(resolvedNewPath);
if (!SystemFile::Rename(resolvedOldPath, resolvedNewPath))
{
return ResultCode::Error;
}
return ResultCode::Success;
}
SystemFile* LocalFileIO::GetFilePointerFromHandle(HandleType fileHandle)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_openFileGuard);
auto openFileIterator = m_openFiles.find(fileHandle);
if (openFileIterator != m_openFiles.end())
{
SystemFile& file = openFileIterator->second;
return &file;
}
return nullptr;
}
HandleType LocalFileIO::GetNextHandle()
{
return m_nextHandle++;
}
static ResultCode DestroyPath_Recurse(LocalFileIO* fileIO, const char* filePath)
{
// this is a deltree command. It needs to eat everything. Even files.
ResultCode res = ResultCode::Success;
fileIO->FindFiles(filePath, "*", [&](const char* iterPath) -> bool
{
// depth first recurse into directories!
// note: findFiles returns full path names.
if (fileIO->IsDirectory(iterPath))
{
// recurse.
if (DestroyPath_Recurse(fileIO, iterPath) != ResultCode::Success)
{
res = ResultCode::Error;
return false; // stop the find files.
}
}
else
{
// if its a file, remove it
if (fileIO->Remove(iterPath) != ResultCode::Success)
{
res = ResultCode::Error;
return false; // stop the find files.
}
}
return true; // continue the find files
});
if (res != ResultCode::Success)
{
return res;
}
// now that we've finished recursing, rmdir on the folder itself
return AZ::IO::SystemFile::DeleteDir(filePath) ? ResultCode::Success : ResultCode::Error;
}
Result LocalFileIO::DestroyPath(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
bool pathExists = Exists(resolvedPath);
if (!pathExists)
{
return ResultCode::Success;
}
if (pathExists && (!IsDirectory(resolvedPath)))
{
return ResultCode::Error;
}
return DestroyPath_Recurse(this, resolvedPath);
}
static void ToUnixSlashes(char* path, AZ::u64 size)
{
auto PrevAndCurrentCharIsPathSeparator = [](const char prev, const char next) -> bool
{
constexpr AZStd::string_view pathSeparator = "/";
const bool prevIsPathSeparator = pathSeparator.find_first_of(prev) != AZStd::string_view::npos;
const bool nextIsPathSeparator = pathSeparator.find_first_of(next) != AZStd::string_view::npos;
return prevIsPathSeparator && nextIsPathSeparator;
};
size_t copyOffset = 0;
for (size_t i = 0; i < size && path[i] != '\0'; i++)
{
if (path[i] == '\\')
{
path[i] = '/';
}
// Replace runs of path separators with one path separator
#if AZ_TRAIT_USE_WINDOWS_FILE_API
// Network file systems for Windows based APIs start with consecutive path separators
// so skip over the first character in this case
constexpr size_t duplicateSeparatorStartOffet = 1;
#else
constexpr size_t duplicateSeparatorStartOffet = 0;
#endif
if (i > duplicateSeparatorStartOffet)
{
if (PrevAndCurrentCharIsPathSeparator(path[i - 1], path[i]))
{
continue;
}
}
// If the copy offset is different than the iteration index of the path, then copy over it over
if (copyOffset != i)
{
path[copyOffset] = path[i];
}
++copyOffset;
}
// Null-terminate the path again in case duplicate slashes were collapsed
path[copyOffset] = '\0';
}
bool LocalFileIO::ResolvePath(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize) const
{
if (resolvedPath == nullptr || resolvedPathSize == 0)
{
return false;
}
resolvedPath[0] = '\0';
if (path == nullptr)
{
return false;
}
if (IsAbsolutePath(path))
{
size_t pathLen = strlen(path);
if (pathLen + 1 < resolvedPathSize)
{
azstrncpy(resolvedPath, resolvedPathSize, path, pathLen + 1);
//see if the absolute path uses @assets@ or @root@, if it does lowercase the relative part
[[maybe_unused]] bool lowercasePath = LowerIfBeginsWith(resolvedPath, resolvedPathSize, GetAlias("@assets@"))
|| LowerIfBeginsWith(resolvedPath, resolvedPathSize, GetAlias("@root@"))
|| LowerIfBeginsWith(resolvedPath, resolvedPathSize, GetAlias("@projectplatformcache@"));
ToUnixSlashes(resolvedPath, resolvedPathSize);
return true;
}
else
{
return false;
}
}
char rootedPathBuffer[AZ_MAX_PATH_LEN] = {0};
const char* rootedPath = path;
// if the path does not begin with an alias, then it is assumed to begin with @assets@
if (path[0] != '@')
{
if (GetAlias("@assets@"))
{
const int rootLength = 9;// strlen("@assets@/")
azstrncpy(rootedPathBuffer, AZ_MAX_PATH_LEN, "@assets@/", rootLength);
size_t pathLen = strlen(path);
size_t rootedPathBufferlength = rootLength + pathLen + 1;// +1 for null terminator
if (rootedPathBufferlength > resolvedPathSize)
{
AZ_Assert(rootedPathBufferlength < resolvedPathSize, "Constructed path length is wrong:%s", rootedPathBuffer);//path constructed is wrong
size_t remainingSize = resolvedPathSize - rootLength - 1;// - 1 for null terminator
azstrncpy(rootedPathBuffer + rootLength, AZ_MAX_PATH_LEN, path, remainingSize);
rootedPathBuffer[resolvedPathSize - 1] = '\0';
}
else
{
azstrncpy(rootedPathBuffer + rootLength, AZ_MAX_PATH_LEN - rootLength, path, pathLen + 1);
}
}
else
{
ConvertToAbsolutePath(path, rootedPathBuffer, AZ_MAX_PATH_LEN);
}
rootedPath = rootedPathBuffer;
}
if (ResolveAliases(rootedPath, resolvedPath, resolvedPathSize))
{
ToUnixSlashes(resolvedPath, resolvedPathSize);
return true;
}
return false;
}
bool LocalFileIO::ResolvePath(AZ::IO::FixedMaxPath& resolvedPath, const AZ::IO::PathView& path) const
{
if (AZ::IO::FixedMaxPathString fixedPath{ path.Native() };
ResolvePath(fixedPath.c_str(), resolvedPath.Native().data(), resolvedPath.Native().capacity()))
{
// Update the null-terminator offset
resolvedPath.Native().resize_no_construct(AZStd::char_traits<char>::length(resolvedPath.Native().data()));
return true;
}
return false;
}
void LocalFileIO::SetAlias(const char* key, const char* path)
{
char fullPath[AZ_MAX_PATH_LEN];
ConvertToAbsolutePath(path, fullPath, AZ_MAX_PATH_LEN);
const auto it = AZStd::find_if(m_aliases.begin(), m_aliases.end(), [key](const AliasType& alias)
{
return alias.first.compare(key) == 0;
});
if (it != m_aliases.end())
{
it->second = fullPath;
}
else
{
m_aliases.emplace_back(key, fullPath);
}
}
const char* LocalFileIO::GetAlias(const char* key) const
{
const auto it = AZStd::find_if(m_aliases.begin(), m_aliases.end(), [key](const AliasType& alias)
{
return alias.first.compare(key) == 0;
});
if (it != m_aliases.end())
{
return it->second.c_str();
}
return nullptr;
}
void LocalFileIO::ClearAlias(const char* key)
{
m_aliases.erase(AZStd::remove_if(m_aliases.begin(), m_aliases.end(), [key](const AliasType& alias)
{
return alias.first.compare(key) == 0;
}), m_aliases.end());
}
AZStd::optional<AZ::u64> LocalFileIO::ConvertToAliasBuffer(char* outBuffer, AZ::u64 outBufferLength, AZStd::string_view inBuffer) const
{
size_t longestMatch = 0;
size_t bufStringLength = inBuffer.size();
AZStd::string_view longestAlias;
for (const auto& [alias, resolvedAlias] : m_aliases)
{
// here we are making sure that the buffer being passed in has enough space to include the alias in it.
// we are trying to find the LONGEST match, meaning of the following two examples, the second should 'win'
// File: g:/O3DE/dev/files/morefiles/blah.xml
// Alias1 links to 'g:/O3DE/dev/'
// Alias2 links to 'g:/O3DE/dev/files/morefiles'
// so returning Alias2 is preferred as it is more specific, even though alias1 includes it.
// note that its not possible for this to be matched if the string is shorter than the length of the alias itself so we skip
// strings that are shorter than the alias's mapped path without checking.
if ((longestMatch == 0) || (resolvedAlias.size() > longestMatch) && (resolvedAlias.size() <= bufStringLength))
{
// custom strcmp that ignores slash directions
constexpr AZStd::string_view pathSeparators{ "/\\" };
bool allMatch = AZStd::equal(resolvedAlias.begin(), resolvedAlias.end(), inBuffer.begin(),
[&pathSeparators](const char lhs, const char rhs)
{
const bool lhsIsSeparator = pathSeparators.find_first_of(lhs) != AZStd::string_view::npos;
const bool rhsIsSeparator = pathSeparators.find_first_of(lhs) != AZStd::string_view::npos;
return (lhsIsSeparator && rhsIsSeparator) || tolower(lhs) == tolower(rhs);
});
if (allMatch)
{
// Either the resolvedAlias path must match the path exactly or the path must have a path separator character
// right after the resolved alias
if (const size_t matchLen = resolvedAlias.size();
matchLen == bufStringLength || (pathSeparators.find_first_of(inBuffer[matchLen]) != AZStd::string_view::npos))
{
longestMatch = matchLen;
longestAlias = alias;
}
}
}
}
if (!longestAlias.empty())
{
// rearrange the buffer to have
// [alias][old path]
size_t aliasSize = longestAlias.size();
size_t charsToAbsorb = longestMatch;
size_t remainingData = bufStringLength - charsToAbsorb;
size_t finalStringSize = aliasSize + remainingData;
if (finalStringSize >= outBufferLength)
{
AZ_Error("FileIO", false, "Alias %.*s cannot fit in output buffer. The output buffer is too small (max len %lu, actual len %zu)",
aznumeric_cast<int>(longestAlias.size()), longestAlias.data(), outBufferLength, finalStringSize);
// avoid buffer overflow, return original untouched
return AZStd::nullopt;
}
// move the remaining piece of the string:
memmove(outBuffer + aliasSize, inBuffer.data() + charsToAbsorb, remainingData);
// copy the alias
memcpy(outBuffer, longestAlias.data(), aliasSize);
/// add a null
outBuffer[finalStringSize] = 0;
return finalStringSize;
}
// If the input and output buffer are different, copy over the input buffer to the output buffer
if (outBuffer != inBuffer.data())
{
if (inBuffer.size() >= outBufferLength)
{
AZ_Error("FileIO", false, R"(Path "%.*s" cannot fit in output buffer. The output buffer is too small (max len %llu, actual len %zu))",
aznumeric_cast<int>(inBuffer.size()), inBuffer.data(), outBufferLength, inBuffer.size());
return AZStd::nullopt;
}
size_t finalStringSize = inBuffer.copy(outBuffer, outBufferLength);
outBuffer[finalStringSize] = 0;
}
return bufStringLength;
}
AZStd::optional<AZ::u64> LocalFileIO::ConvertToAlias(char* inOutBuffer, AZ::u64 bufferLength) const
{
return ConvertToAliasBuffer(inOutBuffer, bufferLength, inOutBuffer);
}
bool LocalFileIO::ConvertToAlias(AZ::IO::FixedMaxPath& convertedPath, const AZ::IO::PathView& pathView) const
{
AZStd::optional<AZ::u64> convertedPathSize =
ConvertToAliasBuffer(convertedPath.Native().data(), convertedPath.Native().capacity(), pathView.Native());
if (convertedPathSize)
{
// Force update of AZStd::fixed_string m_size member to set correct null-terminator offset
convertedPath.Native().resize_no_construct(*convertedPathSize);
return true;
}
return false;
}
bool LocalFileIO::ResolveAliases(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize) const
{
AZ_Assert(path != resolvedPath && resolvedPathSize > strlen(path), "Resolved path is incorrect");
AZ_Assert(path && path[0] != '%', "%% is deprecated, @ is the only valid alias token");
// we assert above, but we also need to properly handle the case when the resolvedPath buffer size
// is too small to copy the source into.
size_t pathLen = strlen(path) + 1; // account for null
if (path == resolvedPath || (resolvedPathSize < pathLen))
{
return false;
}
azstrncpy(resolvedPath, resolvedPathSize, path, pathLen);
for (const auto& alias : m_aliases)
{
const char* key = alias.first.c_str();
size_t keyLen = alias.first.length();
if (azstrnicmp(resolvedPath, key, keyLen) == 0) // we only support aliases at the front of the path
{
[[maybe_unused]] bool lowercasePath = LowerIfBeginsWith(resolvedPath, resolvedPathSize, "@assets@")
|| LowerIfBeginsWith(resolvedPath, resolvedPathSize, "@root@")
|| LowerIfBeginsWith(resolvedPath, resolvedPathSize, "@projectplatformcache@");
const char* dest = alias.second.c_str();
size_t destLen = alias.second.length();
char* afterKey = resolvedPath + keyLen;
size_t afterKeyLen = pathLen - keyLen;
// must ensure that we are replacing the entire folder name, not a partial (e.g. @GAME01@/ vs @GAME0@/)
if (*afterKey == '/' || *afterKey == '\\' || *afterKey == 0)
{
if (afterKeyLen + destLen + 1 < resolvedPathSize)//if after replacing the alias the length is greater than the max path size than skip
{
// scoot the right hand side of the replacement over to make room
memmove(resolvedPath + destLen, afterKey, afterKeyLen + 1); // make sure null is copied
memcpy(resolvedPath, dest, destLen); // insert replacement
pathLen -= keyLen;
pathLen += destLen;
AZStd::replace(resolvedPath, resolvedPath + resolvedPathSize, '\\', '/');
return true;
}
}
}
}
// warn on failing to resolve an alias
AZ_Warning(
"LocalFileIO::ResolveAlias", path && path[0] != '@',
"Failed to resolve an alias: %s", path ? path : "(null)");
return false;
}
bool LocalFileIO::ReplaceAlias(AZ::IO::FixedMaxPath& replacedAliasPath, const AZ::IO::PathView& path) const
{
if (path.empty())
{
replacedAliasPath = path;
return true;
}
AZStd::string_view pathStrView = path.Native();
for (const auto& [aliasKey, aliasValue] : m_aliases)
{
if (AZ::StringFunc::StartsWith(pathStrView, aliasKey))
{
// Reduce of the size result result path by the size of the and add the resolved alias size
AZStd::string_view postAliasView = pathStrView.substr(aliasKey.size());
size_t requiredFixedMaxPathSize = postAliasView.size();
requiredFixedMaxPathSize += aliasValue.size();
// The replaced alias path is greater than 1024 characters, return false
if (requiredFixedMaxPathSize > replacedAliasPath.Native().max_size())
{
return false;
}
replacedAliasPath.Native() = AZ::IO::FixedMaxPathString(AZStd::string_view{ aliasValue });
replacedAliasPath.Native() += postAliasView;
return true;
}
}
if (pathStrView.size() > replacedAliasPath.Native().max_size())
{
return false;
}
replacedAliasPath = path;
return true;
}
bool LocalFileIO::GetFilename(HandleType fileHandle, char* filename, AZ::u64 filenameSize) const
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_openFileGuard);
auto fileIt = m_openFiles.find(fileHandle);
if (fileIt != m_openFiles.end())
{
azstrncpy(filename, filenameSize, fileIt->second.Name(), filenameSize);
return true;
}
return false;
}
bool LocalFileIO::LowerIfBeginsWith(char* inOutBuffer, AZ::u64 bufferLen, const char* alias) const
{
if (alias)
{
AZ::u64 aliasLen = azlossy_caster(strlen(alias));
if (azstrnicmp(inOutBuffer, alias, aliasLen) == 0)
{
for (AZ::u64 i = aliasLen; i < bufferLen && inOutBuffer[i] != '\0'; ++i)
{
inOutBuffer[i] = static_cast<char>(std::tolower(static_cast<int>(inOutBuffer[i])));
}
return true;
}
}
return false;
}
AZ::OSString LocalFileIO::RemoveTrailingSlash(const AZ::OSString& pathStr)
{
if (pathStr.empty() || (pathStr[pathStr.length() - 1] != '/' && pathStr[pathStr.length() - 1] != '\\'))
{
return pathStr;
}
return pathStr.substr(0, pathStr.length() - 1);
}
AZ::OSString LocalFileIO::CheckForTrailingSlash(const AZ::OSString& pathStr)
{
if (pathStr.empty() || pathStr[pathStr.length() - 1] == '/')
{
return pathStr;
}
if (pathStr[pathStr.length() - 1] == '\\')
{
return pathStr.substr(0, pathStr.length() - 1) + "/";
}
return pathStr + "/";
}
} // namespace IO
} // namespace AZ