/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include <StdAfx.h> #include <stdio.h> #include <sys/stat.h> #include <AzCore/std/time.h> #include <string> #include "RemoteFileIO.h" #include "ProjectDefines.h" #include "CryAssert.h" #include <AzFramework/Asset/AssetProcessorMessages.h> #include <AzFramework/Network/AssetProcessorConnection.h> #include <AzCore/Serialization/Utils.h> #include <AzCore/Math/Crc.h> #include <AzCore/std/algorithm.h> // for GetMin() #include <AzCore/std/parallel/lock.h> #include <AzCore/std/functional.h> // for function<> in the find files callback. #ifdef REMOTEFILEIO_CACHE_FILETREE #include <AzCore/std/string/wildcard.h> #endif namespace AZ { namespace IO { using namespace AzFramework::AssetSystem; ////////////////////////////////////////////////////////////////////////// const char* const NetworkFileIOChannel = "NetworkFileIO"; #ifndef REMOTEFILEIO_IS_NETWORKFILEIO const char* const RemoteFileIOChannel = "RemoteFileIO"; const char* const RemoteFileCacheChannel = "RemoteFileCache"; #endif const size_t READ_CHUNK_SIZE = 1024 * 256; #ifdef NETWORKFILEIO_LOG AZ::OSString s_IOLog; AZ::u64 s_fileOperation = 0; class LogCall { public: LogCall(const char* name) :m_name(name) { m_fileOperation = s_fileOperation++; s_IOLog.append(AZStd::string::format("%u Start ", m_fileOperation)); s_IOLog.append(m_name); s_IOLog.append("\r\n"); } void Append(const char* line) { s_IOLog.append(AZStd::string::format("%u ", m_fileOperation)); s_IOLog.append(line); s_IOLog.append("\r\n"); } ~LogCall() { s_IOLog.append(AZStd::string::format("%u End ", m_fileOperation)); s_IOLog.append(m_name); s_IOLog.append("\r\n"); } AZStd::string m_name; AZ::u64 m_fileOperation = 0; }; #define REMOTEFILE_LOG_CALL(x) LogCall lc(x) #define REMOTEFILE_LOG_APPEND(x) lc.Append(x) #else #define REMOTEFILE_LOG_CALL(x) {} #define REMOTEFILE_LOG_APPEND(x) {} #endif NetworkFileIO::NetworkFileIO() { REMOTEFILE_LOG_CALL("NetworkFileIO()::NetworkFileIO()"); } NetworkFileIO::~NetworkFileIO() { REMOTEFILE_LOG_CALL("NetworkFileIO()::~NetworkFileIO()"); AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFilesGuard); while (!m_remoteFiles.empty()) { HandleType fileHandle = m_remoteFiles.begin()->first; Close(fileHandle); } } Result NetworkFileIO::Open(const char* filePath, OpenMode openMode, HandleType& fileHandle) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Open(filepath=%s, openMode=%i, fileHandle=OUT)",filePath?filePath:"nullptr", openMode).c_str()); //error checks if (!filePath) { REMOTEFILE_LOG_APPEND("NetworkFileIO()::Open(filePath=nullptr) return Error"); return ResultCode::Error; } if (openMode == OpenMode::Invalid) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO()::Open(filepath=%s, openMode=%i, fileHandle=OUT) openMode=InvalidMode return Error", filePath, openMode).c_str()); return ResultCode::Error; } //build a request uint32_t mode = static_cast<uint32_t>(openMode); FileOpenRequest request(filePath, mode); FileOpenResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "NetworkFileIO()::Open(filepath=%s, openMode=%i, fileHandle=OUT) unable to send request. return Error", filePath, openMode); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO()::Open(filepath=%s, openMode=%i, fileHandle=OUT) unable to send request. return Error", filePath, openMode).c_str()); return ResultCode::Error; } ResultCode returnValue = static_cast<ResultCode>(response.m_returnCode); if (returnValue == ResultCode::Success) { fileHandle = response.m_fileHandle; //track the open file handles AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFilesGuard); m_remoteFiles.insert(AZStd::make_pair(fileHandle, filePath)); } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO()::Open(filepath=%s, openMode=%i, fileHandle=%u) return %s", filePath, openMode, fileHandle, returnValue == ResultCode::Success ? "Success" : "Error").c_str()); return returnValue; } Result NetworkFileIO::Close(HandleType fileHandle) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Close(fileHandle=%u)", fileHandle).c_str()); //build a request FileCloseRequest request(fileHandle); if (!SendRequest(request)) { AZ_Assert(false, "NetworkFileIO()::Close(fileHandle=%u) Failed to send request. return Error", fileHandle); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO()::Close(fileHandle=%u) Failed to send request. return Error", fileHandle).c_str()); return ResultCode::Error; } //clean up the handle and cache { AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFilesGuard); m_remoteFiles.erase(fileHandle); } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO()::Close(fileHandle=%u) return Success", fileHandle).c_str()); return ResultCode::Success; } Result NetworkFileIO::Tell(HandleType fileHandle, AZ::u64& offset) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Tell(fileHandle=%u, offset=OUT)", fileHandle).c_str()); FileTellRequest request(fileHandle); FileTellResponse responce; if(!SendRequest(request, responce)) { AZ_Assert(false, "NetworkFileIO::Tell(fileHandle=%u) Failed to send tell request. return Error", fileHandle); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Tell(fileHandle=%u) Failed to send tell request. return Error", fileHandle).c_str()); return ResultCode::Error; } ResultCode returnValue = static_cast<ResultCode>(responce.m_resultCode); if (returnValue == ResultCode::Error) { AZ_TracePrintf(NetworkFileIOChannel, "NetworkFileIO::Tell(fileHandle=%u) tell request failed", fileHandle); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Tell(fileHandle=%u) tell request failed", fileHandle).c_str()); return ResultCode::Error; } offset = responce.m_offset; REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Tell(fileHandle=%u) offset=%u return Success", fileHandle, offset).c_str()); return ResultCode::Success; } Result NetworkFileIO::Seek(HandleType fileHandle, AZ::s64 offset, SeekType type) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Seek(fileHandle=%u, offset=%i, type=%s)", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown").c_str()); FileSeekRequest request(fileHandle, static_cast<AZ::u32>(type), offset); if(!SendRequest(request)) { AZ_Assert(false, "NetworkFileIO::Seek() Failed to send request, fileHandle=%u. return Error", fileHandle); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Seek(fileHandle=%u) Failed to send request. return Error", fileHandle).c_str()); return ResultCode::Error; } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Seek(fileHandle=%u) return Success", fileHandle).c_str()); return ResultCode::Success; } Result NetworkFileIO::Size(HandleType fileHandle, AZ::u64& size) { size = 0; REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Size(fileHandle=%u, size=OUT)", fileHandle).c_str()); AZStd::string fileName; { AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFilesGuard); auto remoteIter = m_remoteFiles.find(fileHandle); if (remoteIter != m_remoteFiles.end()) { fileName = remoteIter->second.c_str(); } } if (!fileName.empty()) { return Size(fileName.c_str(), size); } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Size(fileHandle=%u) fileHandle not found! return Error", fileHandle).c_str()); return ResultCode::Error; } Result NetworkFileIO::Size(const char* filePath, AZ::u64& size) { size = 0; REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Size(filePath=%s, size=OUT)", filePath?filePath:"nullptr").c_str()); //error checks if (!filePath) { REMOTEFILE_LOG_APPEND("NetworkFileIO::Size(filePath=nullptr) return Error"); return ResultCode::Error; } if (!strlen(filePath)) { REMOTEFILE_LOG_APPEND("NetworkFileIO::Size(filePath=\"\") strlen(filePath)==0 return Error"); return ResultCode::Error; } FileSizeRequest request(filePath); FileSizeResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "NetworkFileIO::Size(filePath=%s) failed to send request. return Error", filePath); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Size(filePath=%s) failed to send request. return Error", filePath).c_str()); return ResultCode::Error; } ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode); if (returnValue == ResultCode::Error) { AZ_TracePrintf(NetworkFileIOChannel, "NetworkFileIO::Size(filePath=%s) size request failed. return Error", filePath); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Size(filePath=%s) size request failed. return Error", filePath).c_str()); return ResultCode::Error; } size = response.m_size; REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Size(filePath=%s) size=%u. return Success", filePath, size).c_str()); return ResultCode::Success; } Result NetworkFileIO::Read(HandleType fileHandle, void* buffer, AZ::u64 size, bool failOnFewerThanSizeBytesRead, AZ::u64* bytesRead) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Read(filehandle=%i, buffer=OUT, size=%u, failOnFewerThanSizeBytesRead=%s, bytesRead=OUT)", fileHandle, size, failOnFewerThanSizeBytesRead ? "True" : "False").c_str()); size_t remainingBytesToRead = size; AZ::u64 actualRead = 0; while(remainingBytesToRead) { AZ::u64 readSize = GetMin<AZ::u64>(remainingBytesToRead, READ_CHUNK_SIZE); FileReadRequest request(fileHandle, readSize, false); FileReadResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "NetworkFileIO::Read(filehandle=%i, size=%u) request failed. return Error", fileHandle, size); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Read(filehandle=%i, size=%u) request failed. return Error", fileHandle, size).c_str()); return ResultCode::Error; } //note the response could be ANY size, could be less so be careful AZ::u64 responseDataSize = response.m_data.size(); if (responseDataSize <= remainingBytesToRead == false) { AZ_TracePrintf(NetworkFileIOChannel, "NetworkFileIO::Read(filehandle=%i, size=%u) responseDataSize too large!!! responseDataSize=%u <= remainingBytesToRead=%u", fileHandle, size, responseDataSize, remainingBytesToRead); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Read(filehandle=%i, size=%u) responseDataSize too large!!! responseDataSize=%u <= remainingBytesToRead=%u", fileHandle, size, responseDataSize, remainingBytesToRead).c_str()); } ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode); //only copy as much as we can memcpy(buffer, response.m_data.data(), responseDataSize); buffer = reinterpret_cast<char*>(buffer) + responseDataSize; //only reduce by what should have come back remainingBytesToRead -= responseDataSize; //only record read bytes actualRead += responseDataSize; if(bytesRead) { *bytesRead = actualRead; } //if we get an error, we only return an error if failOnFewerThanSizeBytesRead if(returnValue == ResultCode::Error) { AZ_TracePrintf(NetworkFileIOChannel, "NetworkFileIO::Read: request failed, fileHandle=%u", fileHandle); if(failOnFewerThanSizeBytesRead && remainingBytesToRead) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Read(fileHandle=%u, size=%u) actualRead=%u failed On Fewer Than Size Bytes Read. return Error", fileHandle, size, actualRead).c_str()); return ResultCode::Error; } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Read(fileHandle=%u, size=%u) actualRead=%u return Success", fileHandle, size, actualRead).c_str()); return ResultCode::Success; } else if(!responseDataSize) { break; } } if(failOnFewerThanSizeBytesRead && remainingBytesToRead) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Read(fileHandle=%u, size=%u) actualRead=%u failed On Fewer Than Size Bytes Read. return Error", fileHandle, size, actualRead).c_str()); return ResultCode::Error; } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Read(fileHandle=%u, size=%u) actualRead=%u return Success", fileHandle, size, actualRead).c_str()); return ResultCode::Success; } Result NetworkFileIO::Write(HandleType fileHandle, const void* buffer, AZ::u64 size, AZ::u64* bytesWritten) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Write(fileHandle=%u, buffer=OUT, size=%u, bytesWritten=OUT)", fileHandle, size).c_str()); FileWriteRequest request(fileHandle, buffer, size); //always async and just return success unless bytesWritten is set then synchronous if (bytesWritten) { FileWriteResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "NetworkFileIO::Write(fileHandle=%u, size=%u) failed to send sync write request. return Error", fileHandle, size); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Write(fileHandle=%u, size=%u) failed to send sync write request. return Error", fileHandle, size).c_str()); return ResultCode::Error; } if (static_cast<ResultCode>(response.m_resultCode) == ResultCode::Error) { AZ_TracePrintf(NetworkFileIOChannel, "NetworkFileIO::Write(fileHandle=%u, size=%u) sync write request failed. return Error", fileHandle, size); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Write(fileHandle=%u, size=%u) sync write request failed. return Error", fileHandle, size).c_str()); return ResultCode::Error; } *bytesWritten = response.m_bytesWritten; REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Write(fileHandle=%u, size=%u) actualWrite=%u return Success", fileHandle, size, response.m_bytesWritten).c_str()); return ResultCode::Success; } else { // just send the message and assume we wrote it all successfully. if (!SendRequest(request)) { AZ_Assert(false, "NetworkFileIO::Write(fileHandle=%u, size=%u) failed to send async write request. return Error", fileHandle, size); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Write(fileHandle=%u, size=%u) failed to send async write request. return Error", fileHandle, size).c_str()); return ResultCode::Error; } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Write(fileHandle=%u, size=%u) return Success", fileHandle).c_str()); return ResultCode::Success; } } Result NetworkFileIO::Flush(HandleType fileHandle) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Flush(fileHandle=%u)", fileHandle).c_str()); // just send the message, no need to wait for flush response. FileFlushRequest request(fileHandle); if (!SendRequest(request)) { AZ_Assert(false, "NetworkFileIO::Flush(fileHandle=%u) failed to send request. return Error", fileHandle); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Flush(fileHandle=%u) failed to send request. return Error", fileHandle).c_str()); return ResultCode::Error; } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Flush(fileHandle=%u) return Success", fileHandle).c_str()); return ResultCode::Success; } bool NetworkFileIO::Eof(HandleType fileHandle) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Eof(fileHandle=%u)", fileHandle).c_str()); AZ::u64 sizeValue = 0; Result res = Size(fileHandle, sizeValue); AZ::u64 offset = 0; res = Tell(fileHandle, offset); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Eof(fileHandle=%u) return %s", fileHandle, offset >= sizeValue ? "True" : "False").c_str()); return offset >= sizeValue; } bool NetworkFileIO::Exists(const char* filePath) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Exists(filePath=%s)", filePath?filePath:"nullptr").c_str()); //error checks if (!filePath) { REMOTEFILE_LOG_APPEND("NetworkFileIO::Exists(filePath=nullptr) return False"); return false; } if (!strlen(filePath)) { REMOTEFILE_LOG_APPEND("NetworkFileIO::Exists(filePath=\"\") strlen(filePath)==0. return False"); return false; } FileExistsRequest request(filePath); FileExistsResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "NetworkFileIO::Exists(filePath=%s) failed to send request. return False", filePath); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Exists(filePath=%s) failed to send request. return False", filePath).c_str()); return false; } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Exists(filePath=%s) return %s", filePath, response.m_exists ? "True" : "False").c_str()); return response.m_exists; } AZ::u64 NetworkFileIO::ModificationTime(const char* filePath) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::ModificationTime(filePath=%s)", filePath?filePath:"nullptr").c_str()); //error checks if (!filePath) { REMOTEFILE_LOG_APPEND("NetworkFileIO::ModificationTime(filePath=nullptr) return 0"); return 0; } if (!strlen(filePath)) { REMOTEFILE_LOG_APPEND("NetworkFileIO::ModificationTime(filePath=\"\") strlen(filePath)==0. return 0"); return 0; } FileModTimeRequest request(filePath); FileModTimeResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "NetworkFileIO::ModificationTime(filePath=%s) failed to send request. return 0", filePath); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::ModificationTime(filePath=%s) failed to send request. return 0", filePath).c_str()); return 0; } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::ModificationTime(filePath=%s) return %i", filePath, response.m_modTime).c_str()); return response.m_modTime; } AZ::u64 NetworkFileIO::ModificationTime(HandleType fileHandle) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::ModificationTime(fileHandle=%u)", fileHandle).c_str()); { AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFilesGuard); auto remoteIter = m_remoteFiles.find(fileHandle); if (remoteIter != m_remoteFiles.end()) { return ModificationTime(remoteIter->second.c_str()); } } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::ModificationTime(fileHandle=%u) return 0!", fileHandle).c_str()); return 0; } bool NetworkFileIO::IsDirectory(const char* filePath) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::IsDirectory(filePath=%s)", filePath?filePath:"nullptr").c_str()); //error checks if (!filePath) { REMOTEFILE_LOG_APPEND("NetworkFileIO::IsDirectory(filePath=nullptr) filePath=nullptr. return False"); return false; } if (!strlen(filePath)) { REMOTEFILE_LOG_APPEND("NetworkFileIO::IsDirectory(filePath=\"\") strlen(filePath)=0. return False"); return false; } PathIsDirectoryRequest request(filePath); PathIsDirectoryResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "NetworkFileIO::IsDirectory(filePath=%s) failed to send request. return False", filePath); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::IsDirectory(filePath=%s) failed to send request. return False", filePath).c_str()); return false; } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::IsDirectory(filePath=%s) return %s", filePath, response.m_isDir ? "True": "False").c_str()); return response.m_isDir; } bool NetworkFileIO::IsReadOnly(const char* filePath) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::IsReadOnly(filePath=%s)", filePath?filePath:"nullptr").c_str()); //error checks if (!filePath) { REMOTEFILE_LOG_APPEND("NetworkFileIO::IsReadOnly(filePath=nullptr) return False"); return false; } if (!strlen(filePath)) { REMOTEFILE_LOG_APPEND("NetworkFileIO::IsReadOnly(filePath=\"\") strlen(filePath)==0. return False"); return false; } FileIsReadOnlyRequest request(filePath); FileIsReadOnlyResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "NetworkFileIO::IsReadOnly(filePath=%s) failed to send request. return False", filePath); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::IsReadOnly(filePath=%s) failed to send request. return False", filePath).c_str()); return false; } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::IsReadOnly(filePath=%s) return %s", filePath, response.m_isReadOnly ? "True" : "False").c_str()); return response.m_isReadOnly; } Result NetworkFileIO::CreatePath(const char* filePath) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::CreatePath(filePath=%s)", filePath?filePath:"nullptr").c_str()); //error checks if (!filePath) { REMOTEFILE_LOG_APPEND("NetworkFileIO::CreatePath(filePath=nullptr) filePath=nullptr. return Error"); return ResultCode::Error; } if (!strlen(filePath)) { REMOTEFILE_LOG_APPEND("NetworkFileIO::CreatePath(filePath=\"\") strlen(filePath)==0. return Error"); return ResultCode::Error; } PathCreateRequest request(filePath); PathCreateResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "NetworkFileIO::CreatePath(filePath=%s) failed to send request. return Error", filePath); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::CreatePath(filePath=%s) failed to send request. return Error", filePath).c_str()); return ResultCode::Error; } ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::CreatePath(filePath=%s) return %s", filePath, returnValue == ResultCode::Success ? "Success" : "Error").c_str()); return returnValue; } Result NetworkFileIO::DestroyPath(const char* filePath) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::DestroyPath(filePath=%s)", filePath?filePath:"nullptr").c_str()); //error checks if (!filePath) { REMOTEFILE_LOG_APPEND("NetworkFileIO::DestroyPath(filePath=nullptr) filePth=nullptr. return Error"); return ResultCode::Error; } if (!strlen(filePath)) { REMOTEFILE_LOG_APPEND("NetworkFileIO::DestroyPath(filePath=\"\") strlen(filePath)==0. return Error"); return ResultCode::Error; } PathDestroyRequest request(filePath); PathDestroyResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "NetworkFileIO::DestroyPath(filePath=%s) failed to send request. return Error", filePath); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::DestroyPath(filePath=%s) failed to send request. return Error", filePath).c_str()); return ResultCode::Error; } ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::DestroyPath(filePath=%s) return %s", filePath, returnValue == ResultCode::Success ? "Success" : "Error").c_str()); return returnValue; } Result NetworkFileIO::Remove(const char* filePath) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Remove(filePath=%s)", filePath?filePath:"nullptr").c_str()); //error checks if (!filePath) { REMOTEFILE_LOG_APPEND("NetworkFileIO::Remove(filePath=nullptr) filePth=nullptr. return Error"); return ResultCode::Error; } if (!strlen(filePath)) { REMOTEFILE_LOG_APPEND("NetworkFileIO::Remove(filePath=\"\") strlen(filePath)==0. return Error"); return ResultCode::Error; } FileRemoveRequest request(filePath); FileRemoveResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "NetworkFileIO::Remove(filePath=%s) failed to send request. return Error", filePath); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Remove(filePath=%s) failed to send request. return Error", filePath).c_str()); return ResultCode::Error; } ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Remove(filePath=%s) return %s", filePath, returnValue == ResultCode::Success ? "Success" : "Error").c_str()); return returnValue; } Result NetworkFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Copy(sourceFilePath=%s, destinationFilePath=%s)", sourceFilePath?sourceFilePath:"nullptr", destinationFilePath?destinationFilePath:"nullptr").c_str()); //error checks if (!sourceFilePath) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Copy(sourceFilePath=nullptr, destinationFilePath=%s) return Error", destinationFilePath?destinationFilePath:"nullptr").c_str()); return ResultCode::Error; } if (!destinationFilePath) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Copy(sourceFilePath=%s, destinationFilePath=nullptr) return Error", sourceFilePath?sourceFilePath:"nullptr").c_str()); return ResultCode::Error; } if (!strlen(sourceFilePath)) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Copy(sourceFilePath=\"\", destinationFilePath=%s) strlen(sourceFilePath)==0. return Error", destinationFilePath?destinationFilePath:"nullptr").c_str()); return ResultCode::Error; } //fail if the source doesn't exist if (!Exists(sourceFilePath)) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Copy(sourceFilePath=%s, destinationFilePath=%s) Exists(sourceFilePath=%s)==False. return Error", sourceFilePath, destinationFilePath, sourceFilePath).c_str()); return ResultCode::Error; } bool bSourceExcluded = false; bool bDestinationExcluded = false; //else both are remote so just issue the remote copy command FileCopyRequest request(sourceFilePath, destinationFilePath); FileCopyResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "NetworkFileIO::Copy(sourceFilePath=%s, destinationFilePath=%s) failed to send request. return Error", sourceFilePath, destinationFilePath); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Copy() failed to send request for %s -> %s. return Error", sourceFilePath, destinationFilePath).c_str()); return ResultCode::Error; } ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Copy(sourceFilePath=%s, destinationFilePath=%s) return %s", sourceFilePath, destinationFilePath, returnValue == ResultCode::Success ? "Success" : "Error").c_str()); return returnValue; } Result NetworkFileIO::Rename(const char* sourceFilePath, const char* destinationFilePath) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::Rename(sourceFilePath=%s, destinationFilePath=%s)", sourceFilePath?sourceFilePath:"nullptr", destinationFilePath?destinationFilePath:"nullptr").c_str()); //error checks if (!sourceFilePath) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=nullptr, destinationFilePath=%s) sourceFilePath=nullptr. return Error", destinationFilePath?destinationFilePath:"nullptr").c_str()); return ResultCode::Error; } if (!destinationFilePath) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=nullptr) destinationFilePath=nullptr. return Error", sourceFilePath).c_str()); return ResultCode::Error; } if (!strlen(sourceFilePath)) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=\"\", destinationFilePath=%s) strlen(sourceFilePath)==0. return Error", destinationFilePath).c_str()); return ResultCode::Error; } //fail if the source doesn't exist if (!Exists(sourceFilePath)) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=%s) Exists(sourceFilePath)=False. return Error", sourceFilePath, destinationFilePath).c_str()); return ResultCode::Error; } if (!strlen(destinationFilePath)) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=\"\") strlen(destinationFilePath)==0. return Error", sourceFilePath).c_str()); return ResultCode::Error; } //we are going to access shared memory so lock and copy the results into our memory bool bSourceExcluded = false; bool bDestinationExcluded = false; //if the source and destination are the same, shortcut if (!strcmp(sourceFilePath, destinationFilePath)) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=%s) sourceFilePath=destinationFilePath. return Error", sourceFilePath, destinationFilePath).c_str()); return ResultCode::Error; } //if the destination exists if (Exists(destinationFilePath)) { //if its read only fail if (IsReadOnly(destinationFilePath)) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=%s) IsReadOnly(destinationFilePath)=True. return Error", sourceFilePath, destinationFilePath).c_str()); return ResultCode::Error; } } //else both are remote so just issue the remote command FileRenameRequest request(sourceFilePath, destinationFilePath); FileRenameResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=%s) failed to send request. return Error", sourceFilePath, destinationFilePath); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=%s) failed to send request. return Error", sourceFilePath, destinationFilePath).c_str()); return ResultCode::Error; } ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::Rename(sourceFilePath=%s, destinationFilePath=%s) return %s", sourceFilePath, destinationFilePath, returnValue == ResultCode::Success ? "Success" : "Error").c_str()); return returnValue; } Result NetworkFileIO::FindFiles(const char* filePath, const char* filter, FindFilesCallbackType callback) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::FindFiles(filePath=%s, filter=%s, callback=OUT)", filePath?filePath:"nullptr", filter?filter:"nullptr").c_str()); //error checks if (!filePath) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::FindFiles(filePath=nullptr, filter=%s) filePath=nullptr. return Error", filter?filter:"nulltpr").c_str()); return ResultCode::Error; } if (!filter) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::FindFiles(filePath=%s, filter=nullptr) filter=nullptr. return Error", filePath).c_str()); return ResultCode::Error; } if (!strlen(filePath)) { REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::FindFiles(filePath=\"\", filter=%s) strlen(filePath)==0. return Error", filter).c_str()); return ResultCode::Error; } FindFilesRequest request(filePath, filter); FindFilesResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "NetworkFileIO::FindFiles(filePath=%s, filter=%s) could not send request. return Error", filePath, filter); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::FindFiles(filePath=%s, filter=%s) could not send request. return Error", filePath, filter).c_str()); return ResultCode::Error; } ResultCode returnValue = static_cast<ResultCode>(response.m_resultCode); if (returnValue == ResultCode::Success) { // callbacks const uint64_t numFiles = response.m_files.size(); for (uint64_t fileIdx = 0; fileIdx < numFiles; ++fileIdx) { const char* fileName = response.m_files[fileIdx].c_str(); if (!callback(fileName)) { fileIdx = numFiles;//we are done } } } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::FindFiles(filePath=%s, filter=%s) return %s", filePath, filter, returnValue == ResultCode::Success ? "Success" : "Error").c_str()); return returnValue; } void NetworkFileIO::SetAlias(const char* alias, const char* path) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::SetAlias(alias=%s, path=%s)", alias?alias:"nullptr", path?path:"nullptr").c_str()); } const char* NetworkFileIO::GetAlias(const char* alias) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::GetAlias(alias=%s)", alias?alias:"nullptr").c_str()); REMOTEFILE_LOG_APPEND("NetworkFileIO::GetAlias() return nullptr"); return nullptr; } void NetworkFileIO::ClearAlias(const char* alias) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::ClearAlias(alias=%s)", alias?alias:"nullptr").c_str()); } AZ::u64 NetworkFileIO::ConvertToAlias(char* inOutBuffer, AZ::u64 bufferLength) const { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::ConvertToAlias(inOutBuffer=%s, bufferLength=%u)", inOutBuffer?inOutBuffer:"nullptr", bufferLength).c_str()); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::ConvertToAlias() return %u", strlen(inOutBuffer)).c_str()); return strlen(inOutBuffer); } bool NetworkFileIO::ResolvePath(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize) { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::ResolvePath(path=%s, resolvedPath=%s, resolvedPathsize=%u)", path?path:"nullptr", resolvedPath?resolvedPath:"nullptr", resolvedPathSize).c_str()); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::ResolvePath(path=%s, resolvedPath=%s, resolvedPathsize=%u) return False", path?path:"nullptr", resolvedPath?resolvedPath:"nullptr", resolvedPathSize).c_str()); return false; } bool NetworkFileIO::GetFilename(HandleType fileHandle, char* filename, AZ::u64 filenameSize) const { REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::GetFilename(fileHandle=%u, filename=%s, filenamesize=%u)", fileHandle, filename?filename:"nullptr", filenameSize).c_str()); { AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFilesGuard); const auto fileIt = m_remoteFiles.find(fileHandle); if (fileIt != m_remoteFiles.end()) { if (filenameSize >= fileIt->second.length()) { azstrncpy(filename, filenameSize, fileIt->second.c_str(), fileIt->second.length()); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO()::GetFilename(fileHandle=%u, filename=%s, filenamesize=%u) return True", fileHandle, filename?filename:"nullptr", filenameSize).c_str()); return true; } else { AZ_TracePrintf(NetworkFileIOChannel, "NetworkFileIO::GetFilename(fileHandle=%u, filename=%s, filenamesize=%u) Result buffer is too small %u", fileHandle, filename?filename:"nullptr", filenameSize, fileIt->second.length()); REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::GetFilename(fileHandle=%u, filename=%s, filenamesize=%u) Result buffer is too small %u", fileHandle, filename?filename:"nullptr", filenameSize, fileIt->second.length()).c_str()); } } } REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO()::GetFilename(fileHandle=%u, filename=%s, filenamesize=%u) return False", fileHandle, filename?filename:"nullptr", filenameSize).c_str()); return false; } bool NetworkFileIO::IsRemoteIOEnabled() { REMOTEFILE_LOG_CALL("NetworkFileIO()::IsRemoteIOEnabled()"); REMOTEFILE_LOG_APPEND("NetworkFileIO()::IsRemoteIOEnabled() return True"); return true; } #ifndef REMOTEFILEIO_IS_NETWORKFILEIO ////////////////////////////////////////////////////////////////////////// const int REFRESH_FILESIZE_TIME = 500;// ms const size_t CACHE_LOOKAHEAD_SIZE = 1024 * 256; RemoteFileCache::RemoteFileCache(RemoteFileCache&& other) { REMOTEFILE_LOG_CALL("RemoteFileCache()::RemoteFileCache(other)"); *this = AZStd::move(other); } AZ::IO::RemoteFileCache& RemoteFileCache::operator=(RemoteFileCache&& other) { REMOTEFILE_LOG_CALL("RemoteFileCache()::operator=(other)"); if (this != &other) { m_cacheLookaheadBuffer = AZStd::move(other.m_cacheLookaheadBuffer); m_cacheLookaheadPos = other.m_cacheLookaheadPos; m_fileSize = other.m_fileSize; m_fileSizeTime = other.m_fileSizeTime; m_filePosition = other.m_filePosition; m_fileHandle = other.m_fileHandle; m_openMode = other.m_openMode; } return *this; } void RemoteFileCache::Invalidate() { m_cacheLookaheadPos = 0; m_cacheLookaheadBuffer.clear(); } AZ::u64 RemoteFileCache::RemainingBytes() { return m_cacheLookaheadBuffer.size() - m_cacheLookaheadPos; } AZ::u64 RemoteFileCache::CacheFilePosition() { return m_filePosition - RemainingBytes(); } AZ::u64 RemoteFileCache::CacheStartFilePosition() { return m_filePosition - m_cacheLookaheadBuffer.size(); } AZ::u64 RemoteFileCache::CacheEndFilePosition() { return m_filePosition; } bool RemoteFileCache::IsFilePositionInCache(AZ::u64 filePosition) { return filePosition >= CacheStartFilePosition() && filePosition < CacheEndFilePosition(); } void RemoteFileCache::SetCachePositionFromFilePosition(AZ::u64 filePosition) { m_cacheLookaheadPos = filePosition - CacheStartFilePosition(); } void RemoteFileCache::SyncCheck() { #ifdef REMOTEFILEIO_SYNC_CHECK //don't sync check files open for write //they can be written to asynchronously so tell may return a different position than is cached //because we dont wait for the network request to finish before we immediately ask for tell if (AnyFlag(m_openMode & OpenMode::ModeWrite) || AnyFlag(m_openMode & OpenMode::ModeAppend) || AnyFlag(m_openMode & OpenMode::ModeUpdate)) { return; } REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileCache()::SyncCheck(m_fileHandle=%u)", m_fileHandle).c_str()); FileTellRequest request(m_fileHandle); FileTellResponse responce; if (!SendRequest(request, responce)) { AZ_Assert(false, "RemoteFileCache::SyncCheck(m_fileHandle=%u) Failed to send tell request.", m_fileHandle); REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileCache::SyncCheck(m_fileHandle=%u) Failed to send tell request.", m_fileHandle).c_str()); } ResultCode returnValue = static_cast<ResultCode>(responce.m_resultCode); if (returnValue == ResultCode::Error) { AZ_TracePrintf(RemoteFileCacheChannel, "RemoteFileCache::SyncCheck(m_fileHandle=%u) tell request failed.", m_fileHandle); REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileCache::SyncCheck(m_fileHandle=%u) tell request failed.", m_fileHandle).c_str()); } if (responce.m_offset != m_filePosition) { AZ_TracePrintf(RemoteFileCacheChannel, "RemoteFileCache::SyncCheck(m_fileHandle=%u) failed!!! m_filePosition=%u tell=%u", m_fileHandle, m_filePosition, responce.m_offset); REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileCache::SyncCheck(m_fileHandle=%u) failed!!! m_filePosition=%u tell=%u", m_fileHandle, m_filePosition, responce.m_offset).c_str()); } else { REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileCache::SyncCheck(m_fileHandle=%u) Success m_filePosition=%u tell=%u", m_fileHandle, m_filePosition, responce.m_offset).c_str()); } #endif } void RemoteFileCache::SetFilePosition(AZ::u64 filePosition) { m_filePosition = filePosition; SyncCheck(); } void RemoteFileCache::OffsetFilePosition(AZ::s64 offset) { m_filePosition += offset; SyncCheck(); } bool RemoteFileCache::Eof() { return CacheFilePosition() == m_fileSize; } ////////////////////////////////////////////////////////////////////////// RemoteFileIO::RemoteFileIO(FileIOBase* excludedFileIO) { REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::RemoteFileIO()").c_str()); m_excludedFileIO = excludedFileIO; #ifdef REMOTEFILEIO_CACHE_FILETREE CacheFileTree(); #endif } RemoteFileIO::~RemoteFileIO() { delete m_excludedFileIO; //for now delete it, when We change to always create local file io We won't #ifdef REMOTEFILEIO_CACHE_FILETREE AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); #endif REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::RemoteFileIO()").c_str()); } Result RemoteFileIO::Open(const char* filePath, OpenMode openMode, HandleType& fileHandle) { REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Open(filePath=%s, openMode=%i, fileHandle=OUT)", filePath?filePath:"nullptr", openMode).c_str()); Result returnValue = NetworkFileIO::Open(filePath, openMode, fileHandle); if (returnValue == ResultCode::Success) { { AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard); m_remoteFileCache.insert(fileHandle); RemoteFileCache& cache = GetCache(fileHandle); cache.m_fileHandle = fileHandle; cache.m_openMode = openMode; } } REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Open(filePath=%s, fileHandle=%u) return %s", filePath?filePath:"nullptr", fileHandle, returnValue == ResultCode::Success ? "Success" : "Error").c_str()); return returnValue; } Result RemoteFileIO::Close(HandleType fileHandle) { REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Close(fileHandle=%u)", fileHandle).c_str()); Result returnValue = NetworkFileIO::Close(fileHandle); if (returnValue == ResultCode::Success) { AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard); m_remoteFileCache.erase(fileHandle); } REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Open(fileHandle=%u) return %s", fileHandle, returnValue == ResultCode::Success ? "Success" : "Error").c_str()); return returnValue; } Result RemoteFileIO::Tell(HandleType fileHandle, AZ::u64& offset) { REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Tell(fileHandle=%u, offset=OUT)", fileHandle).c_str()); { AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard); RemoteFileCache& cache = GetCache(fileHandle); offset = cache.CacheFilePosition(); } REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Tell(fileHandle=%u, offset=%i) return Success", fileHandle, offset).c_str()); return ResultCode::Success; } Result RemoteFileIO::Seek(HandleType fileHandle, AZ::s64 offset, SeekType type) { REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Seek(fileHandle=%u, offset=%i, type=%s)", fileHandle, offset, type==SeekType::SeekFromCurrent ? "SeekFromCurrent" : type==SeekType::SeekFromEnd ? "SeekFromEnd" : type==SeekType::SeekFromStart ? "SeekFromStart" : "Unknown").c_str()); AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard); RemoteFileCache& cache = GetCache(fileHandle); //if we land in the cache all we need to do is adjust the cache position and return //calculate the new position in the file AZ::s64 newFilePosition = 0; if (type == AZ::IO::SeekType::SeekFromCurrent) { newFilePosition = cache.CacheFilePosition() + offset; } else if (type == AZ::IO::SeekType::SeekFromStart) { newFilePosition = offset; } else if (type == AZ::IO::SeekType::SeekFromEnd) { AZ::u64 fileSize = 0; if(Size(fileHandle, fileSize)== ResultCode::Error) { return ResultCode::Error; } newFilePosition = fileSize + offset; } else { AZ_Assert(false, "RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) unknown seektype. return Error", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown"); REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) unknown seektype. return Error", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown").c_str()); return ResultCode::Error; } //bound check //note that seeking beyond end or before beginning is system dependent //therefore we will define that on all platforms it is not allowed if (newFilePosition < 0) { AZ_TracePrintf(RemoteFileIOChannel, "RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) seek to a position before the begining of a file!", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown"); REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) seek to a position before the begining of a file!", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown").c_str()); newFilePosition = 0; } else { AZ::u64 fileSize = 0; Size(fileHandle, fileSize); if (newFilePosition > fileSize) { AZ_TracePrintf(RemoteFileIOChannel, "RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) seek to a position after the end of a file!", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown"); REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) seek to a position after the end of a file!", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown").c_str()); newFilePosition = fileSize; } } //see if the new calculated position is in the cache if (cache.IsFilePositionInCache(newFilePosition)) { //it is, so calculate what the cache position should be given the new file position and //set it and return success cache.SetCachePositionFromFilePosition(newFilePosition); cache.SyncCheck(); return ResultCode::Success; } else if (newFilePosition == cache.m_filePosition) { cache.SyncCheck(); return ResultCode::Success; } //we didn't land in the cache //perform the seek for real, invalidate and set new file position //note when setting a new absolute position we always use SeekFromStart, not the passed in seek type FileSeekRequest request(fileHandle, static_cast<AZ::u32>(AZ::IO::SeekType::SeekFromStart), newFilePosition); if (!SendRequest(request)) { AZ_Assert(false, "RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) Failed to send request. return Error", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown"); REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) Failed to send request. return Error", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown").c_str()); return ResultCode::Error; } cache.Invalidate(); cache.SetFilePosition(newFilePosition); REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Seek(fileHandle=%u, offset=%i, type=%s) return Success", fileHandle, offset, type == SeekType::SeekFromCurrent ? "SeekFromCurrent" : type == SeekType::SeekFromEnd ? "SeekFromEnd" : type == SeekType::SeekFromStart ? "SeekFromStart" : "Unknown").c_str()); return ResultCode::Success; } Result RemoteFileIO::Size(HandleType fileHandle, AZ::u64& size) { REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO::Size(fileHandle=%u, size=OUT)", fileHandle).c_str()); AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard); RemoteFileCache& cache = GetCache(fileHandle); // do we even have to check? AZ::u64 msNow = AZStd::GetTimeUTCMilliSecond(); if (msNow - cache.m_fileSizeTime > REFRESH_FILESIZE_TIME) { if (NetworkFileIO::Size(fileHandle, cache.m_fileSize)) { cache.m_fileSizeTime = msNow; size = cache.m_fileSize; REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Size(fileHandle=%u) size=%u. return Success", fileHandle, size).c_str()); return ResultCode::Success; } } else { size = cache.m_fileSize; REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Size(fileHandle=%u) size=%u return Success", fileHandle, size).c_str()); return ResultCode::Success; } REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Size(fileHandle=%u) return Error", fileHandle).c_str()); return ResultCode::Error; } Result RemoteFileIO::Read(HandleType fileHandle, void* buffer, AZ::u64 size, bool failOnFewerThanSizeBytesRead, AZ::u64* bytesRead) { REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Read(fileHandle=%u, buffer=OUT, size=%u, failOnFewerThanSizeBytesRead=%s, bytesRead=OUT)", fileHandle, size, failOnFewerThanSizeBytesRead ? "True" : "False").c_str()); AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard); RemoteFileCache& cache = GetCache(fileHandle); AZ::u64 remainingBytesToRead = size; AZ::u64 bytesReadFromCache = 0; AZ::u64 remainingBytesInCache = cache.RemainingBytes(); if (remainingBytesInCache) { AZ::u64 bytesToReadFromCache = AZStd::GetMin<AZ::u64>(remainingBytesInCache, remainingBytesToRead); memcpy(buffer, cache.m_cacheLookaheadBuffer.data() + cache.m_cacheLookaheadPos, bytesToReadFromCache); bytesReadFromCache = bytesToReadFromCache; remainingBytesToRead -= bytesToReadFromCache; cache.m_cacheLookaheadPos += bytesToReadFromCache; } REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u) bytesReadFromCache=%u remainingBytesToRead=%u", fileHandle, bytesReadFromCache, remainingBytesToRead).c_str()); AZ::u64 bytesThatHaveBeenRead = bytesReadFromCache; if (bytesRead) { *bytesRead = bytesThatHaveBeenRead; } buffer = reinterpret_cast<char*>(buffer) + bytesReadFromCache; if (remainingBytesToRead) { AZ::u64 actualRead = 0; Result returnValue = NetworkFileIO::Read(fileHandle, buffer, remainingBytesToRead, true, &actualRead); remainingBytesToRead -= actualRead; if (actualRead) { cache.OffsetFilePosition(actualRead); } bytesThatHaveBeenRead += actualRead; if (bytesRead) { *bytesRead = bytesThatHaveBeenRead; } REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u, size=%u) %s remainingBytesToRead=%u. actualRead=%u", fileHandle, remainingBytesToRead, returnValue==ResultCode::Success ? "Success" : "Fail", remainingBytesToRead, actualRead).c_str()); //if we get an error, we only return an error if failOnFewerThanSizeBytesRead if (returnValue == ResultCode::Error) { if (failOnFewerThanSizeBytesRead && remainingBytesToRead) { REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u, bytesThatHaveBeenRead=%u) failOnFewerThanSizeBytesRead. return Error", fileHandle, bytesThatHaveBeenRead).c_str()); return ResultCode::Error; } REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u, bytesThatHaveBeenRead=%u) return Success", fileHandle, bytesThatHaveBeenRead).c_str()); return ResultCode::Success; } } //they could have asked for more bytes than there is in the file if (failOnFewerThanSizeBytesRead && remainingBytesToRead && Eof(fileHandle)) { REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u, bytesThatHaveBeenRead=%u) failOnFewerThanSizeBytesRead. return Error", fileHandle, bytesThatHaveBeenRead).c_str()); return ResultCode::Error; } //if we get here we have satisfied the read request. //if the cache is empty try to refill it if not eof. if (!cache.RemainingBytes() && !Eof(fileHandle)) { //make sure the cache file size is up to date AZ::u64 fsize = 0; Size(fileHandle, fsize); AZ::u64 remainingFileBytes = cache.m_fileSize - cache.m_filePosition; AZ::u64 readSize = AZStd::GetMin<AZ::u64>(remainingFileBytes, CACHE_LOOKAHEAD_SIZE); cache.m_cacheLookaheadBuffer.clear(); cache.m_cacheLookaheadBuffer.resize_no_construct(readSize); REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u, size=%u) -=CACHE READ=-", fileHandle, readSize).c_str()); AZ::u64 actualRead = 0; Result returnValue = NetworkFileIO::Read(fileHandle, cache.m_cacheLookaheadBuffer.data(), readSize, false, &actualRead); if (actualRead) { cache.m_cacheLookaheadBuffer.resize(actualRead); cache.OffsetFilePosition(actualRead); cache.m_cacheLookaheadPos = 0; } if (returnValue == ResultCode::Error) { AZ_TracePrintf(RemoteFileIOChannel, "RemoteFileIO::Read(fileHandle=%u, size=%u) -=CACHE READ=- actualRead=%i Failed", fileHandle, readSize, actualRead); } REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u, size=%u) -=CACHE READ=- actualRead=%i %s", fileHandle, readSize, actualRead, returnValue == ResultCode::Success ? "Success" : "Fail").c_str()); } REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Read(fileHandle=%u, bytesThatHaveBeenRead=%u) return Success", fileHandle, bytesThatHaveBeenRead).c_str()); return ResultCode::Success; } Result RemoteFileIO::Write(HandleType fileHandle, const void* buffer, AZ::u64 size, AZ::u64* bytesWritten) { REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Write(fileHandle=%u, buffer=OUT, size=%u, bytesWritten=OUT)", fileHandle, size).c_str()); // We need to seek back to where we should be in the file before we commit a write. // This is unnecessary if the cache is empty, or we're at the end of the cache. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard); RemoteFileCache& cache = GetCache(fileHandle); if (cache.m_cacheLookaheadBuffer.size() && cache.RemainingBytes()) { // find out where we are AZ::u64 seekPosition = cache.CacheFilePosition(); // note, seeks are predicted, and do not ask for a response. FileSeekRequest request(fileHandle, static_cast<AZ::u32>(AZ::IO::SeekType::SeekFromStart), seekPosition); if (!SendRequest(request)) { AZ_Assert(false, "RemoteFileIO::Write(fileHandle=%u, size=%u) Seek Failed to send request. return Error", fileHandle, size); REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Write(fileHandle=%u, size=%u) Seek Failed to send request. return Error", fileHandle, size).c_str()); return ResultCode::Error; } cache.SetFilePosition(seekPosition); } cache.Invalidate(); REMOTEFILE_LOG_APPEND("RemoteFileIO::Write() cache.Invalidate()"); cache.m_fileSizeTime = 0; // invalidate file size after write. FileWriteRequest request(fileHandle, buffer, size); //always async and just return success unless bytesWritten is set then synchronous if (bytesWritten) { FileWriteResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "RemoteFileIO::Write(fileHandle=%u, size=%u) failed to send sync write request. return Error", fileHandle, size); REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Write(fileHandle=%u, size=%u) failed to send sync write request. return Error", fileHandle, size).c_str()); return ResultCode::Error; } ResultCode res = static_cast<ResultCode>(response.m_resultCode); if (res == ResultCode::Error) { AZ_TracePrintf(RemoteFileIOChannel, "RemoteFileIO::Write(fileHandle=%u, size=%u) sync write request failed", fileHandle, size); } *bytesWritten = response.m_bytesWritten; cache.OffsetFilePosition(response.m_bytesWritten); } else { // just send the message and assume we wrote it all successfully. if (!SendRequest(request)) { AZ_Assert(false, "RemoteFileIO::Write(fileHandle=%u, size=%u) failed to send async write request. return Error", fileHandle, size); REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Write(fileHandle=%u, size=%u) failed to send async write request. return Error", fileHandle, size).c_str()); return ResultCode::Error; } cache.OffsetFilePosition(size); } REMOTEFILE_LOG_APPEND("RemoteFileIO::Write() Success"); return ResultCode::Success; } bool RemoteFileIO::Eof(HandleType fileHandle) { REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Eof(fileHandle=%u)", fileHandle).c_str()); //make sure the cache file size is up to date AZ::u64 size = 0; Size(fileHandle, size); AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard); RemoteFileCache& cache = GetCache(fileHandle); bool isEof = cache.Eof(); REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Eof(fileHandle=%u) return %s", fileHandle, isEof ? "True" : "False").c_str()); return isEof; } //get cache should only be called while AZStd::lock_guard<AZStd::recursive_mutex> lock(m_remoteFileCacheGuard); AZ::IO::RemoteFileCache& RemoteFileIO::GetCache(HandleType fileHandle) { auto found = m_remoteFileCache.find(fileHandle); // check this because it is a serious error since it may be that you're in an unguarded access to a non-existent handle. AZ_Assert(found != m_remoteFileCache.end(), "RemoteFileIO::GetCache(fileHandle=%u) Missing! Did something go wrong with open?", fileHandle); return found->second; } void RemoteFileIO::SetAlias(const char* alias, const char* path) { if (m_excludedFileIO) { m_excludedFileIO->SetAlias(alias, path); } } const char* RemoteFileIO::GetAlias(const char* alias) { return m_excludedFileIO ? m_excludedFileIO->GetAlias(alias) : nullptr; } void RemoteFileIO::ClearAlias(const char* alias) { if (m_excludedFileIO) { m_excludedFileIO->ClearAlias(alias); } } AZ::u64 RemoteFileIO::ConvertToAlias(char* inOutBuffer, AZ::u64 bufferLength) const { return m_excludedFileIO ? m_excludedFileIO->ConvertToAlias(inOutBuffer, bufferLength) : strlen(inOutBuffer); } bool RemoteFileIO::ResolvePath(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize) { return m_excludedFileIO ? m_excludedFileIO->ResolvePath(path, resolvedPath, resolvedPathSize) : false; } #ifdef REMOTEFILEIO_CACHE_FILETREE bool RemoteFileIO::Exists(const char* filePath) { REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::Exists(filePath=%s)", filePath?filePath:"nullptr").c_str()); //error checks if (!filePath) { REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::Exists(filePath=nullptr) filePath=nullptr. return False").c_str()); return false; } if (!strlen(filePath)) { REMOTEFILE_LOG_APPEND("RemoteFileIO::Exists(filePath=\"\") strlen(filePath)==0. return False"); return false; } AZStd::string filePathName(filePath); AZStd::replace(filePathName.begin(), filePathName.end(), '\\', '/'); AZStd::size_t lastNonSlash = filePathName.find_last_not_of('/'); AZStd::size_t lastSlash = filePathName.find_last_of('/'); if (lastSlash != AZStd::string::npos && lastSlash > lastNonSlash) { filePathName.erase(lastSlash); } filePath = filePathName.c_str(); const uint64_t numFiles = m_remoteFileTreeCache.size(); for (uint64_t fileIdx = 0; fileIdx < numFiles; ++fileIdx) { const char* fileName = m_remoteFileTreeCache[fileIdx].c_str(); if (!azstricmp(filePath, fileName)) { return true; } } const uint64_t numFolders = m_remoteFolderTreeCache.size(); for (uint64_t folderIdx = 0; folderIdx < numFolders; ++folderIdx) { const char* folderName = m_remoteFolderTreeCache[folderIdx].c_str(); if (!azstricmp(filePath, folderName)) { return true; } } #ifdef REMOTEFILEIO_CACHE_FILETREE_FALLBACK //fall back bool bExists = NetworkFileIO::Exists(filePath); if (bExists) { //it wasn't found before, but it is now, update the cache if (NetworkFileIO::IsDirectory(filePath)) { m_remoteFolderTreeCache.push_back(filePath); } else { m_remoteFileTreeCache.push_back(filePath); } } return bExists; #else return false; #endif } bool RemoteFileIO::IsDirectory(const char* filePath) { REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::IsDirectory(filePath=%s)", filePath?filepath:"nullptr").c_str()); //error checks if (!filePath) { REMOTEFILE_LOG_APPEND("RemoteFileIO::IsDirectory(filePath=nullptr) filePath=nullptr. return False"); return false; } if (!strlen(filePath)) { REMOTEFILE_LOG_APPEND("RemoteFileIO::IsDirectory(filePath=\"\") strlen(filePath)==0. return False"); return false; } AZStd::string filePathName(filePath); AZStd::replace(filePathName.begin(), filePathName.end(), '\\', '/'); AZStd::size_t lastNonSlash = filePathName.find_last_not_of('/'); AZStd::size_t lastSlash = filePathName.find_last_of('/'); if (lastSlash != AZStd::string::npos && lastSlash > lastNonSlash) { filePathName.erase(lastSlash); } filePath = filePathName.c_str(); const uint64_t numFolders = m_remoteFolderTreeCache.size(); for (uint64_t folderIdx = 0; folderIdx < numFolders; ++folderIdx) { const char* folderName = m_remoteFolderTreeCache[folderIdx].c_str(); if (!azstricmp(filePath, folderName)) { return true; } } #ifdef REMOTEFILEIO_CACHE_FILETREE_FALLBACK //fallback bool bExists = NetworkFileIO::IsDirectory(filePath); if (bExists) { //it wasn't found before, but it is now, update the cache m_remoteFolderTreeCache.push_back(filePath); } return bExists; #else return false; #endif } Result RemoteFileIO::FindFiles(const char* filePath, const char* filter, FindFilesCallbackType callback) { REMOTEFILE_LOG_CALL(AZStd::string::format("RemoteFileIO()::FindFiles(filePath=%s, filter=%s, callback=OUT)", filePath?filePath:"nullptr", filter?filter:"nullptr").c_str()); //error checks if (!filePath) { REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::FindFiles(filePath=nullptr, filter=%s) filePath=nullptr. return Error", filter?filter:"nullptr").c_str()); return ResultCode::Error; } if (!filter) { REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::FindFiles(filePath=%s, filter=nullptr) filter=nullptr. return Error", filePath).c_str()); return ResultCode::Error; } if (!strlen(filePath)) { REMOTEFILE_LOG_APPEND(AZStd::string::format("RemoteFileIO::FindFiles(filePath=\"\", filter=%s) strlen(filePath)==0. return Error", filter).c_str()); return ResultCode::Error; } AZStd::string filePathName(filePath); AZStd::replace(filePathName.begin(), filePathName.end(), '\\', '/'); AZStd::size_t lastNonSlash = filePathName.find_last_not_of('/'); AZStd::size_t lastSlash = filePathName.find_last_of('/'); if (lastSlash != AZStd::string::npos && lastSlash > lastNonSlash) { filePathName.erase(lastSlash); } filePath = filePathName.c_str(); AZ::u64 filePathLen = filePathName.length(); // files callbacks const uint64_t numFiles = m_remoteFileTreeCache.size(); for (uint64_t fileIdx = 0; fileIdx < numFiles; ++fileIdx) { const char* cachedFilePath = m_remoteFileTreeCache[fileIdx].c_str(); if (!azstrnicmp(filePath, cachedFilePath, filePathLen)) { if (strlen(cachedFilePath) > filePathLen) { const char* cachedFileName = cachedFilePath + filePathLen + 1; if (!strchr(cachedFileName, '/')) { //no slash was found so this file is in this folder if (AZStd::wildcard_match(filter, cachedFileName)) { if (!callback(cachedFilePath)) { return ResultCode::Success; } } } } } } // folders const uint64_t numFolders = m_remoteFolderTreeCache.size(); for (uint64_t folderIdx = 0; folderIdx < numFolders; ++folderIdx) { const char* cachedFolderPath = m_remoteFolderTreeCache[folderIdx].c_str(); if (!azstrnicmp(filePath, cachedFolderPath, filePathLen)) { if (strlen(cachedFolderPath) > filePathLen) { const char* cachedFolderName = cachedFolderPath + filePathLen + 1; if (!strchr(cachedFolderName, '/')) { //no slash was found so this is not a sub folder if (AZStd::wildcard_match(filter, cachedFolderName)) { if (!callback(cachedFolderPath)) { return ResultCode::Success; } } } } } } return ResultCode::Success; } Result RemoteFileIO::CacheFileTree() { m_remoteFileTreeCache.clear(); m_remoteFolderTreeCache.clear(); REMOTEFILE_LOG_CALL("RemoteFileIO()::CacheFileTree()"); FileTreeRequest request; FileTreeResponse response; if (!SendRequest(request, response)) { AZ_Assert(false, "RemoteFileIO::CacheFileTree() could not send request. return Error"); REMOTEFILE_LOG_APPEND("RemoteFileIO::CacheFileTree() could not send request. return Error"); return ResultCode::Error; } Result returnValue = static_cast<ResultCode>(response.m_resultCode); if (returnValue == ResultCode::Success) { for (auto& it: response.m_fileList) { AZStd::replace(it.begin(), it.end(), '\\', '/'); m_remoteFileTreeCache.push_back(it); } for (auto& it: response.m_folderList) { AZStd::replace(it.begin(), it.end(), '\\', '/'); AZStd::size_t lastNonSlash = it.find_last_not_of('/'); AZStd::size_t lastSlash = it.find_last_of('/'); if (lastSlash != AZStd::string::npos && lastSlash > lastNonSlash) { it.erase(lastSlash); } m_remoteFolderTreeCache.push_back(it); } } AzFramework::AssetCatalogEventBus::Handler::BusConnect(); return returnValue; } //========================================================================= // AssetCatalogEventBus::OnCatalogAssetChanged //========================================================================= void RemoteFileIO::OnCatalogAssetChanged(const AZ::Data::AssetId& assetId) { AZ_UNUSED(assetId); CacheFileTree(); } //========================================================================= // AssetSystemBus::OnCatalogAssetRemoved //========================================================================= void RemoteFileIO::OnCatalogAssetRemoved(const AZ::Data::AssetId& assetId) { AZ_UNUSED(assetId); CacheFileTree(); } #endif //REMOTEFILEIO_CACHE_FILETREE #endif //REMOTEFILEIO_IS_NETWORKFILEIO } // namespace IO }//namespace AZ