Files
UnrealEngineUWP/Engine/Source/Developer/Virtualization/Private/VirtualizationFileBackend.cpp

177 lines
5.2 KiB
C++
Raw Normal View History

// Copyright Epic Games, Inc. All Rights Reserved.
#include "VirtualizationFileBackend.h"
#include "HAL/FileManager.h"
#include "Misc/Parse.h"
#include "Misc/Paths.h"
#include "Virtualization/VirtualizationManager.h"
#include "VirtualizationUtilities.h"
namespace UE::Virtualization
{
Improve error handling and logging in the file and jupiter Mirage backends. #rb Per.Larsson #rnx #preflight 60f9212d1f926d0001d41223 * VirtualizationJupiterBackend - When pulling a payload we should assume that a 400 error response when trying to GET the payload header means that the payload is not in Jupiter. -- Not being able to find the payload should not log an error, instead we can make a note of it in the verbose log (similar to the file system backend) * VirtualizationFileBackend - Moved the formatting of system errors to it's own function. - Log the system error when failing to write a payload during a push as well as a pull. - We now check that the FileArchive wrote correctly to disk and delete the output file and fail the push if it did not. -- A future piece of work will change the logic to write to a tmp file at the root of the file store and them move the file to the final location to cut down on the potential of leaving corrupted files around (similar to the process when we save packages) * Perforce - The FDownloadFile command now takes an optional parameter EVerbosity that can allow the caller to choose the level of logging output that the command will generate. - The source control backend for Mirage now opts to supress the logging of the full perforce command when we are pulling payloads as we can generate many hundreds or thousands of requests and the info is not useful to users. -- We continue to log the command when validating the depot as this is the most likely command to fail so having the info in the log may prove useful. #ROBOMERGE-SOURCE: CL 16921815 in //UE5/Main/... #ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v836-16769935) [CL 16921818 by paul chipchase in ue5-release-engine-test branch]
2021-07-22 05:45:28 -04:00
/**
* Fill in the given string builder with the human readable message of the current system
* code, followed by the code value itself.
* In the system value is currently 0, then we assume that it was cleared before this was
* able to be called and write that the error is unknown instead of assuming that the
* operation was a success.
*/
void GetFormattedSystemError(FStringBuilderBase& SystemErrorMessage)
{
SystemErrorMessage.Reset();
const uint32 SystemError = FPlatformMisc::GetLastError();
// If we have a system error we can give a more informative error message but don't output it if the error is zero as
// this can lead to very confusing error messages.
if (SystemError != 0)
{
TCHAR SystemErrorMsg[MAX_SPRINTF] = { 0 };
FPlatformMisc::GetSystemErrorMessage(SystemErrorMsg, sizeof(SystemErrorMsg), SystemError);
SystemErrorMessage.Appendf(TEXT("'%s' (%d)"), SystemErrorMsg, SystemError);
}
else
{
SystemErrorMessage << TEXT("'unknown reason' (0)");
}
}
FFileSystemBackend::FFileSystemBackend(FStringView ConfigName)
: IVirtualizationBackend(EOperations::Both)
{
Name = WriteToString<256>(TEXT("FFileSystemBackend - "), ConfigName).ToString();
}
bool FFileSystemBackend::Initialize(const FString& ConfigEntry)
{
if (!FParse::Value(*ConfigEntry, TEXT("Path="), RootDirectory))
{
UE_LOG(LogVirtualization, Error, TEXT("[%s] 'Path=' not found in the config file"), *GetDebugString());
return false;
}
FPaths::NormalizeDirectoryName(RootDirectory);
if (RootDirectory.IsEmpty())
{
UE_LOG(LogVirtualization, Error, TEXT("[%s] Config file entry 'Path=' was empty"), *GetDebugString());
return false;
}
// TODO: Validate that the given path is usable?
UE_LOG(LogVirtualization, Log, TEXT("[%s] Using path: '%s'"), *GetDebugString(), *RootDirectory);
return true;
}
EPushResult FFileSystemBackend::PushData(const FPayloadId& Id, const FCompressedBuffer& Payload)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FFileSystemBackend::PushData);
if (DoesExist(Id))
{
UE_LOG(LogVirtualization, Verbose, TEXT("[%s] Already has a copy of the payload '%s'."), *GetDebugString(), *Id.ToString());
return EPushResult::PayloadAlreadyExisted;
}
TStringBuilder<512> FilePath;
CreateFilePath(Id, FilePath);
// TODO: Should we write to a temp file and then move it once it has written?
TUniquePtr<FArchive> FileAr(IFileManager::Get().CreateFileWriter(FilePath.ToString()));
if (FileAr == nullptr)
{
TStringBuilder<MAX_SPRINTF> SystemErrorMsg;
GetFormattedSystemError(SystemErrorMsg);
UE_LOG(LogVirtualization, Error, TEXT("[%s] Failed to write payload '%s' to '%s' due to system error: %s"),
*GetDebugString(),
*Id.ToString(),
FilePath.ToString(),
SystemErrorMsg.ToString());
return EPushResult::Failed;
}
for (const FSharedBuffer& Buffer : Payload.GetCompressed().GetSegments())
{
// Const cast because FArchive requires a non-const pointer!
FileAr->Serialize(const_cast<void*>(Buffer.GetData()), static_cast<int64>(Buffer.GetSize()));
}
if (FileAr->Close())
{
return EPushResult::Success;
}
else
{
// TODO: If we were first saving to a tmp file we could avoid the need to delete the
// potentially corrupt output file.
IFileManager::Get().Delete(FilePath.ToString());
return EPushResult::Failed;
}
}
FCompressedBuffer FFileSystemBackend::PullData(const FPayloadId& Id)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FFileSystemBackend::PullData);
TStringBuilder<512> FilePath;
CreateFilePath(Id, FilePath);
// TODO: Should we allow the error severity to be configured via ini or just not report this case at all?
if (!IFileManager::Get().FileExists(FilePath.ToString()))
{
UE_LOG(LogVirtualization, Verbose, TEXT("[%s] Does not contain the payload '%s'"), *GetDebugString(), *Id.ToString());
return FCompressedBuffer();
}
TUniquePtr<FArchive> FileAr(IFileManager::Get().CreateFileReader(FilePath.ToString()));
if (FileAr == nullptr)
{
TStringBuilder<MAX_SPRINTF> SystemErrorMsg;
GetFormattedSystemError(SystemErrorMsg);
UE_LOG(LogVirtualization, Error, TEXT("[%s] Failed to load payload '%s' from file '%s' due to system error: %s"),
*GetDebugString(),
*Id.ToString(),
FilePath.ToString(),
SystemErrorMsg.ToString());
return FCompressedBuffer();
}
return FCompressedBuffer::FromCompressed(*FileAr);
}
FString FFileSystemBackend::GetDebugString() const
{
return Name;
}
bool FFileSystemBackend::DoesExist(const FPayloadId& Id)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FFileSystemBackend::DoesExist);
TStringBuilder<512> FilePath;
CreateFilePath(Id, FilePath);
return IFileManager::Get().FileExists(FilePath.ToString());
}
void FFileSystemBackend::CreateFilePath(const FPayloadId& PayloadId, FStringBuilderBase& OutPath)
{
TStringBuilder<52> PayloadPath;
Utils::PayloadIdToPath(PayloadId, PayloadPath);
OutPath << RootDirectory << TEXT("/") << PayloadPath;
}
UE_REGISTER_VIRTUALIZATION_BACKEND_FACTORY(FFileSystemBackend, FileSystem);
} // namespace UE::Virtualization