2021-04-30 08:14:54 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "Virtualization/IVirtualizationBackend.h"
# include "Containers/StringView.h"
# include "HAL/FileManager.h"
# include "Misc/Parse.h"
# include "Misc/Paths.h"
2021-05-03 02:47:29 -04:00
# include "Misc/StringBuilder.h"
2021-04-30 08:14:54 -04:00
# include "Virtualization/VirtualizationManager.h"
2021-05-03 05:01:37 -04:00
# include "VirtualizationUtilities.h"
2021-04-30 08:14:54 -04:00
namespace UE : : Virtualization
{
/**
* A basic backend based on the file system . This can be used to access / store virtualization
* data either on a local disk or a network share . It is intended to be used as a caching system
* to speed up operations ( running a local cache or a shared cache for a site ) rather than as the
* proper backend solution .
*
* Ini file setup :
* ' Name ' = ( Type = FileSystem , Path = " XXX " )
* Where ' Name ' is the backend name in the hierarchy and ' XXX ' if the path to the directory where
* you want to files to be stored .
*/
class FFileSystemBackend : public IVirtualizationBackend
{
public :
FFileSystemBackend ( FStringView ConfigName )
: IVirtualizationBackend ( EOperations : : Both )
{
2021-05-03 02:47:29 -04:00
Name = WriteToString < 256 > ( TEXT ( " FFileSystemBackend - " ) , ConfigName ) . ToString ( ) ;
2021-04-30 08:14:54 -04:00
}
virtual ~ FFileSystemBackend ( ) = default ;
private :
virtual bool Initialize ( const FString & ConfigEntry ) override
{
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 ;
}
2021-05-19 07:46:07 -04:00
virtual EPushResult PushData ( const FPayloadId & Id , const FCompressedBuffer & Payload ) override
2021-04-30 08:14:54 -04:00
{
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 ( ) ) ;
2021-05-19 07:46:07 -04:00
return EPushResult : : PayloadAlreadyExisted ;
2021-04-30 08:14:54 -04:00
}
2021-05-03 02:47:29 -04:00
TStringBuilder < 512 > FilePath ;
CreateFilePath ( Id , FilePath ) ;
2021-04-30 08:14:54 -04:00
{
// TODO: Should we write to a temp file and then move it once it has written?
2021-05-03 02:47:29 -04:00
TUniquePtr < FArchive > FileAr ( IFileManager : : Get ( ) . CreateFileWriter ( FilePath . ToString ( ) ) ) ;
2021-04-30 08:14:54 -04:00
if ( FileAr = = nullptr )
{
2021-05-03 02:47:29 -04:00
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to push payload '%s' to '%s' " ) , * GetDebugString ( ) , * Id . ToString ( ) , FilePath . ToString ( ) ) ;
2021-05-19 07:46:07 -04:00
return EPushResult : : Failed ;
2021-04-30 08:14:54 -04:00
}
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 ( ) ) ) ;
}
}
2021-05-19 07:46:07 -04:00
return EPushResult : : Success ;
2021-04-30 08:14:54 -04:00
}
virtual FCompressedBuffer PullData ( const FPayloadId & Id ) override
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FFileSystemBackend : : PullData ) ;
2021-05-03 02:47:29 -04:00
TStringBuilder < 512 > FilePath ;
CreateFilePath ( Id , FilePath ) ;
2021-04-30 08:14:54 -04:00
// TODO: Should we allow the error severity to be configured via ini or just not report this case at all?
2021-05-03 02:47:29 -04:00
if ( ! IFileManager : : Get ( ) . FileExists ( FilePath . ToString ( ) ) )
2021-04-30 08:14:54 -04:00
{
UE_LOG ( LogVirtualization , Verbose , TEXT ( " [%s] Does not contain the payload '%s' " ) , * GetDebugString ( ) , * Id . ToString ( ) ) ;
return FCompressedBuffer ( ) ;
}
2021-05-03 02:47:29 -04:00
TUniquePtr < FArchive > FileAr ( IFileManager : : Get ( ) . CreateFileReader ( FilePath . ToString ( ) ) ) ;
2021-04-30 08:14:54 -04:00
if ( FileAr = = nullptr )
{
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 [ 2048 ] = { 0 } ;
FPlatformMisc : : GetSystemErrorMessage ( SystemErrorMsg , sizeof ( SystemErrorMsg ) , SystemError ) ;
2021-05-03 02:47:29 -04:00
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to load payload '%s' file '%s' due to system error: '%s' (%d)) " ) , * GetDebugString ( ) , * Id . ToString ( ) , FilePath . ToString ( ) , SystemErrorMsg , SystemError ) ;
2021-04-30 08:14:54 -04:00
}
else
{
2021-05-03 02:47:29 -04:00
UE_LOG ( LogVirtualization , Error , TEXT ( " [%s] Failed to load payload '%s' from '%s' (reason unknown) " ) , * GetDebugString ( ) , * Id . ToString ( ) , FilePath . ToString ( ) ) ;
2021-04-30 08:14:54 -04:00
}
return FCompressedBuffer ( ) ;
}
return FCompressedBuffer : : FromCompressed ( * FileAr ) ;
}
bool DoesExist ( const FPayloadId & Id )
{
TRACE_CPUPROFILER_EVENT_SCOPE ( FFileSystemBackend : : DoesExist ) ;
2021-05-03 02:47:29 -04:00
TStringBuilder < 512 > FilePath ;
CreateFilePath ( Id , FilePath ) ;
return IFileManager : : Get ( ) . FileExists ( FilePath . ToString ( ) ) ;
2021-04-30 08:14:54 -04:00
}
virtual FString GetDebugString ( ) const override
{
return Name ;
}
2021-05-03 02:47:29 -04:00
void CreateFilePath ( const FPayloadId & PayloadId , FStringBuilderBase & OutPath )
2021-04-30 08:14:54 -04:00
{
2021-05-03 02:47:29 -04:00
TStringBuilder < 52 > PayloadPath ;
Utils : : PayloadIdToPath ( PayloadId , PayloadPath ) ;
OutPath < < RootDirectory < < TEXT ( " / " ) < < PayloadPath ;
2021-04-30 08:14:54 -04:00
}
private :
2021-05-03 02:47:29 -04:00
2021-04-30 08:14:54 -04:00
FString Name ;
FString RootDirectory ;
} ;
UE_REGISTER_VIRTUALIZATION_BACKEND_FACTORY ( FFileSystemBackend , FileSystem ) ;
} // namespace UE::Virtualization