2020-03-12 13:29:21 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
2023-06-21 15:23:06 -04:00
# include "DerivedDataCacheStore.h"
2022-06-07 11:17:10 -04:00
# include "DerivedDataLegacyCacheStore.h"
2020-03-12 13:29:21 -04:00
2020-03-12 18:00:07 -04:00
# if WITH_S3_DDC_BACKEND
2022-05-27 07:04:22 -04:00
# if PLATFORM_MICROSOFT
# include "Microsoft/AllowMicrosoftPlatformTypes.h"
2020-03-12 13:29:21 -04:00
# endif
2022-05-27 07:04:22 -04:00
# if PLATFORM_MICROSOFT
# include "Microsoft/HideMicrosoftPlatformTypes.h"
2020-03-12 13:29:21 -04:00
# endif
2022-01-11 11:57:38 -05:00
2020-03-12 13:29:21 -04:00
# include "Async/ParallelFor.h"
2022-06-07 11:17:10 -04:00
# include "DerivedDataBackendInterface.h"
2021-04-28 16:22:18 -04:00
# include "DerivedDataCacheRecord.h"
2022-02-11 12:57:03 -05:00
# include "DerivedDataCachePrivate.h"
# include "DerivedDataChunk.h"
2022-01-11 11:57:38 -05:00
# include "DesktopPlatformModule.h"
2020-03-12 13:29:21 -04:00
# include "Dom/JsonObject.h"
# include "HAL/PlatformFile.h"
2020-05-06 17:58:18 -04:00
# include "HAL/PlatformFileManager.h"
2022-02-11 12:57:03 -05:00
# include "HashingArchiveProxy.h"
2022-01-11 11:57:38 -05:00
# include "Memory/SharedBuffer.h"
# include "Misc/Base64.h"
2024-01-18 09:56:55 -05:00
# include "Misc/Compression.h"
2020-03-26 17:03:08 -04:00
# include "Misc/ConfigCacheIni.h"
2022-01-11 11:57:38 -05:00
# include "Misc/FeedbackContext.h"
# include "Misc/FileHelper.h"
# include "Misc/Paths.h"
# include "Misc/SecureHash.h"
# include "ProfilingDebugging/CountersTrace.h"
# include "ProfilingDebugging/CpuProfilerTrace.h"
2022-02-11 12:57:03 -05:00
# include "Serialization/CompactBinary.h"
# include "Serialization/CompactBinaryPackage.h"
# include "Serialization/CompactBinaryValidation.h"
2022-01-11 11:57:38 -05:00
# include "Serialization/JsonReader.h"
# include "Serialization/JsonSerializer.h"
# include "curl/curl.h"
2020-03-12 13:29:21 -04:00
2021-04-28 16:22:18 -04:00
# if WITH_SSL
# include "Ssl.h"
2022-09-13 21:48:33 -04:00
# include <openssl/hmac.h>
# include <openssl/sha.h>
# include <openssl/ssl.h>
2021-04-28 16:22:18 -04:00
# endif
2020-03-12 13:29:21 -04:00
# define S3DDC_BACKEND_WAIT_INTERVAL 0.01f
# define S3DDC_HTTP_REQUEST_TIMEOUT_SECONDS 30L
# define S3DDC_HTTP_REQUEST_TIMOUT_ENABLED 1
# define S3DDC_REQUEST_POOL_SIZE 16
# define S3DDC_MAX_FAILED_LOGIN_ATTEMPTS 16
# define S3DDC_MAX_ATTEMPTS 4
# define S3DDC_MAX_BUFFER_RESERVE 104857600u
2022-02-14 14:43:39 -05:00
namespace UE : : DerivedData
2021-04-28 16:22:18 -04:00
{
2024-02-07 12:51:56 -05:00
TRACE_DECLARE_ATOMIC_INT_COUNTER ( S3DDC_Get , TEXT ( " S3DDC Get " ) ) ;
TRACE_DECLARE_ATOMIC_INT_COUNTER ( S3DDC_GetHit , TEXT ( " S3DDC Get Hit " ) ) ;
2020-03-12 13:29:21 -04:00
2022-02-11 12:57:03 -05:00
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2022-02-14 14:43:39 -05:00
void BuildPathForCachePackage ( const FCacheKey & CacheKey , FStringBuilderBase & Path ) ;
void BuildPathForCacheContent ( const FIoHash & RawHash , FStringBuilderBase & Path ) ;
2020-03-12 13:29:21 -04:00
class FStringAnsi
{
public :
FStringAnsi ( )
{
Inner . Add ( 0 ) ;
}
FStringAnsi ( const ANSICHAR * Text )
{
Inner . Append ( Text , FCStringAnsi : : Strlen ( Text ) + 1 ) ;
}
void Append ( ANSICHAR Character )
{
Inner [ Inner . Num ( ) - 1 ] = Character ;
Inner . Add ( 0 ) ;
}
void Append ( const FStringAnsi & Other )
{
Inner . RemoveAt ( Inner . Num ( ) - 1 ) ;
Inner . Append ( Other . Inner ) ;
}
void Append ( const ANSICHAR * Text )
{
Inner . RemoveAt ( Inner . Num ( ) - 1 ) ;
Inner . Append ( Text , FCStringAnsi : : Strlen ( Text ) + 1 ) ;
}
void Append ( const ANSICHAR * Start , const ANSICHAR * End )
{
Inner . RemoveAt ( Inner . Num ( ) - 1 ) ;
2021-11-10 13:08:00 -05:00
Inner . Append ( Start , UE_PTRDIFF_TO_INT32 ( End - Start ) ) ;
2020-03-12 13:29:21 -04:00
Inner . Add ( 0 ) ;
}
static FStringAnsi Printf ( const ANSICHAR * Format , . . . )
{
ANSICHAR Buffer [ 1024 ] ;
2023-09-29 13:00:07 -04:00
GET_TYPED_VARARGS ( ANSICHAR , Buffer , UE_ARRAY_COUNT ( Buffer ) , UE_ARRAY_COUNT ( Buffer ) - 1 , Format , Format ) ;
2020-03-12 13:29:21 -04:00
return Buffer ;
}
FString ToWideString ( ) const
{
return ANSI_TO_TCHAR ( Inner . GetData ( ) ) ;
}
const ANSICHAR * operator * ( ) const
{
return Inner . GetData ( ) ;
}
int32 Len ( ) const
{
return Inner . Num ( ) - 1 ;
}
private :
TArray < ANSICHAR > Inner ;
} ;
struct FSHA256
{
uint8 Digest [ 32 ] ;
FStringAnsi ToString ( ) const
{
ANSICHAR Buffer [ 65 ] ;
for ( int Idx = 0 ; Idx < 32 ; Idx + + )
{
FCStringAnsi : : Sprintf ( Buffer + ( Idx * 2 ) , " %02x " , Digest [ Idx ] ) ;
}
return Buffer ;
}
} ;
FSHA256 Sha256 ( const uint8 * Input , size_t InputLen )
{
FSHA256 Output ;
SHA256 ( Input , InputLen , Output . Digest ) ;
return Output ;
}
FSHA256 HmacSha256 ( const uint8 * Input , size_t InputLen , const uint8 * Key , size_t KeyLen )
{
FSHA256 Output ;
unsigned int OutputLen = 0 ;
HMAC ( EVP_sha256 ( ) , Key , KeyLen , ( const unsigned char * ) Input , InputLen , Output . Digest , & OutputLen ) ;
return Output ;
}
FSHA256 HmacSha256 ( const FStringAnsi & Input , const uint8 * Key , size_t KeyLen )
{
return HmacSha256 ( ( const uint8 * ) * Input , ( size_t ) Input . Len ( ) , Key , KeyLen ) ;
}
FSHA256 HmacSha256 ( const char * Input , const uint8 * Key , size_t KeyLen )
{
return HmacSha256 ( ( const uint8 * ) Input , ( size_t ) FCStringAnsi : : Strlen ( Input ) , Key , KeyLen ) ;
}
bool IsSuccessfulHttpResponse ( long ResponseCode )
{
return ( ResponseCode > = 200 & & ResponseCode < = 299 ) ;
}
struct IRequestCallback
{
2022-06-07 11:17:10 -04:00
virtual ~ IRequestCallback ( ) = default ;
2020-03-12 13:29:21 -04:00
virtual bool Update ( int NumBytes , int TotalBytes ) = 0 ;
} ;
2022-01-11 11:57:38 -05:00
/**
* Backend for a read - only AWS S3 based caching service .
* */
2022-06-07 11:17:10 -04:00
class FS3CacheStore final : public ILegacyCacheStore
2022-01-11 11:57:38 -05:00
{
public :
/**
2022-06-07 11:17:10 -04:00
* Creates the cache store , checks health status and attempts to acquire an access token .
2022-01-11 11:57:38 -05:00
*
* @ param InRootManifestPath Local path to the JSON manifest in the workspace containing a list of files to download
* @ param InBaseUrl Base URL for the bucket , with trailing slash ( eg . https : //foo.s3.us-east-1.amazonaws.com/)
* @ param InRegion Name of the AWS region ( eg . us - east - 1 )
* @ param InCanaryObjectKey Key for a canary object used to test whether this backend is usable
* @ param InCachePath Path to cache the DDC files
*/
2023-06-21 15:23:06 -04:00
FS3CacheStore ( const TCHAR * InName , const TCHAR * InRootManifestPath , const TCHAR * InBaseUrl , const TCHAR * InRegion , const TCHAR * InCanaryObjectKey , const TCHAR * InCachePath , ICacheStoreOwner & Owner ) ;
~ FS3CacheStore ( ) final ;
2022-06-07 11:17:10 -04:00
inline const FString & GetName ( ) const { return BaseUrl ; }
2022-01-11 11:57:38 -05:00
/**
2022-06-07 11:17:10 -04:00
* Checks if cache store is usable ( reachable and accessible ) .
2022-01-11 11:57:38 -05:00
* @ return true if usable
*/
2022-06-07 11:17:10 -04:00
inline bool IsUsable ( ) const { return bEnabled ; }
2022-01-11 11:57:38 -05:00
2022-06-07 11:17:10 -04:00
// ICacheStore
2022-02-14 14:43:39 -05:00
void Put (
2022-01-11 11:57:38 -05:00
TConstArrayView < FCachePutRequest > Requests ,
IRequestOwner & Owner ,
2022-02-14 14:43:39 -05:00
FOnCachePutComplete & & OnComplete ) final ;
void Get (
2022-01-11 11:57:38 -05:00
TConstArrayView < FCacheGetRequest > Requests ,
IRequestOwner & Owner ,
2022-02-14 14:43:39 -05:00
FOnCacheGetComplete & & OnComplete ) final ;
void PutValue (
TConstArrayView < FCachePutValueRequest > Requests ,
IRequestOwner & Owner ,
FOnCachePutValueComplete & & OnComplete ) final ;
void GetValue (
2022-02-11 12:57:03 -05:00
TConstArrayView < FCacheGetValueRequest > Requests ,
IRequestOwner & Owner ,
2022-02-14 14:43:39 -05:00
FOnCacheGetValueComplete & & OnComplete ) final ;
void GetChunks (
2022-01-18 04:47:59 -05:00
TConstArrayView < FCacheGetChunkRequest > Requests ,
2022-01-11 11:57:38 -05:00
IRequestOwner & Owner ,
2022-02-14 14:43:39 -05:00
FOnCacheGetChunkComplete & & OnComplete ) final ;
2022-01-11 11:57:38 -05:00
2022-06-07 11:17:10 -04:00
// ILegacyCacheStore
void LegacyStats ( FDerivedDataCacheStatsNode & OutNode ) final ;
bool LegacyDebugOptions ( FBackendDebugOptions & Options ) final ;
2022-01-11 11:57:38 -05:00
private :
struct FBundle ;
struct FBundleEntry ;
struct FBundleDownload ;
struct FRootManifest ;
class FHttpRequest ;
class FRequestPool ;
FString RootManifestPath ;
FString BaseUrl ;
FString Region ;
FString CanaryObjectKey ;
FString CacheDir ;
2023-06-21 15:23:06 -04:00
ICacheStoreOwner & StoreOwner ;
ICacheStoreStats * StoreStats = nullptr ;
2022-01-11 11:57:38 -05:00
TArray < FBundle > Bundles ;
TUniquePtr < FRequestPool > RequestPool ;
bool bEnabled ;
2022-02-11 12:57:03 -05:00
[[nodiscard]] FOptionalCacheRecord GetCacheRecordOnly (
FStringView Name ,
const FCacheKey & Key ,
2023-06-21 15:23:06 -04:00
const FCacheRecordPolicy & Policy ,
FRequestStats & Stats ) ;
2022-02-11 12:57:03 -05:00
[[nodiscard]] FOptionalCacheRecord GetCacheRecord (
FStringView Name ,
const FCacheKey & Key ,
const FCacheRecordPolicy & Policy ,
2023-06-21 15:23:06 -04:00
EStatus & OutStatus ,
FRequestStats & Stats ) ;
2022-02-11 12:57:03 -05:00
2023-06-21 15:23:06 -04:00
[[nodiscard]] bool GetCacheValueOnly ( FStringView Name , const FCacheKey & Key , ECachePolicy Policy , FValue & OutValue , FRequestStats & Stats ) ;
[[nodiscard]] bool GetCacheValue ( FStringView Name , const FCacheKey & Key , ECachePolicy Policy , FValue & OutValue , FRequestStats & Stats ) ;
2022-02-11 12:57:03 -05:00
2023-06-21 15:23:06 -04:00
[[nodiscard]] bool GetCacheContentExists ( const FCacheKey & Key , const FIoHash & RawHash , FRequestStats & Stats ) const ;
2022-02-11 12:57:03 -05:00
[[nodiscard]] bool GetCacheContent (
FStringView Name ,
const FCacheKey & Key ,
const FValueId & Id ,
const FValue & Value ,
ECachePolicy Policy ,
2023-06-21 15:23:06 -04:00
FValue & OutValue ,
FRequestStats & Stats ) const ;
2022-02-11 12:57:03 -05:00
void GetCacheContent (
FStringView Name ,
const FCacheKey & Key ,
const FValueId & Id ,
const FValue & Value ,
ECachePolicy Policy ,
FCompressedBufferReader & Reader ,
2023-06-21 15:23:06 -04:00
TUniquePtr < FArchive > & OutArchive ,
FRequestStats & Stats ) const ;
2022-02-11 12:57:03 -05:00
void BuildCachePackagePath ( const FCacheKey & CacheKey , FStringBuilderBase & Path ) const ;
void BuildCacheContentPath ( const FIoHash & RawHash , FStringBuilderBase & Path ) const ;
2023-06-21 15:23:06 -04:00
[[nodiscard]] bool LoadFileWithHash ( FStringBuilderBase & Path , FStringView DebugName , FRequestStats & Stats , TFunctionRef < void ( FArchive & ) > ReadFunction ) const ;
[[nodiscard]] bool LoadFile ( FStringBuilderBase & Path , FStringView DebugName , FRequestStats & Stats , TFunctionRef < void ( FArchive & ) > ReadFunction ) const ;
[[nodiscard]] TUniquePtr < FArchive > OpenFile ( FStringBuilderBase & Path , FStringView DebugName , FRequestStats & Stats ) const ;
2022-02-11 12:57:03 -05:00
2023-06-21 15:23:06 -04:00
[[nodiscard]] bool FileExists ( FStringBuilderBase & Path , FRequestStats & Stats ) const ;
2022-02-11 12:57:03 -05:00
2022-01-11 11:57:38 -05:00
bool DownloadManifest ( const FRootManifest & RootManifest , FFeedbackContext * Context ) ;
void RemoveUnusedBundles ( ) ;
void ReadBundle ( FBundle & Bundle ) ;
FBackendDebugOptions DebugOptions ;
} ;
2020-03-12 13:29:21 -04:00
/**
* Minimal HTTP request type wrapping CURL without the need for managers . This request
* is written to allow reuse of request objects , in order to allow connections to be reused .
*
* CURL has a global library initialization ( curl_global_init ) . We rely on this happening in
* the Online / HTTP library which is a dependency on this module .
*/
2022-02-14 14:43:39 -05:00
class FS3CacheStore : : FHttpRequest
2020-03-12 13:29:21 -04:00
{
public :
2021-04-28 16:22:18 -04:00
FHttpRequest ( const ANSICHAR * InRegion , const ANSICHAR * InAccessKey , const ANSICHAR * InSecretKey )
2020-03-12 13:29:21 -04:00
: Region ( InRegion )
, AccessKey ( InAccessKey )
, SecretKey ( InSecretKey )
{
Curl = curl_easy_init ( ) ;
}
2021-04-28 16:22:18 -04:00
~ FHttpRequest ( )
2020-03-12 13:29:21 -04:00
{
curl_easy_cleanup ( Curl ) ;
}
/**
* Performs the request , blocking until finished .
* @ param Url HTTP URL to fetch
* @ param Callback Object used to convey state to / from the operation
* @ param Buffer Optional buffer to directly receive the result of the request .
* If unset the response body will be stored in the request .
*/
long PerformBlocking ( const ANSICHAR * Url , IRequestCallback * Callback , TArray < uint8 > & OutResponseBody , FOutputDevice * Log )
{
TRACE_CPUPROFILER_EVENT_SCOPE ( S3DDC_CurlPerform ) ;
// Find the host from the URL
const ANSICHAR * ProtocolEnd = FCStringAnsi : : Strchr ( Url , ' : ' ) ;
check ( ProtocolEnd ! = nullptr & & * ( ProtocolEnd + 1 ) = = ' / ' & & * ( ProtocolEnd + 2 ) = = ' / ' ) ;
const ANSICHAR * UrlHost = ProtocolEnd + 3 ;
const ANSICHAR * UrlHostEnd = FCStringAnsi : : Strchr ( UrlHost , ' / ' ) ;
check ( UrlHostEnd ! = nullptr ) ;
FStringAnsi Host ;
Host . Append ( UrlHost , UrlHostEnd ) ;
// Get the header strings
FDateTime Timestamp = FDateTime : : UtcNow ( ) ; // FDateTime(2015, 9, 15, 12, 45, 0);
FStringAnsi TimeString = FStringAnsi : : Printf ( " %04d%02d%02dT%02d%02d%02dZ " , Timestamp . GetYear ( ) , Timestamp . GetMonth ( ) , Timestamp . GetDay ( ) , Timestamp . GetHour ( ) , Timestamp . GetMinute ( ) , Timestamp . GetSecond ( ) ) ;
// Payload string
FStringAnsi EmptyPayloadSha256 = Sha256 ( nullptr , 0 ) . ToString ( ) ;
// Create the headers
curl_slist * CurlHeaders = nullptr ;
CurlHeaders = curl_slist_append ( CurlHeaders , * FStringAnsi : : Printf ( " Host: %s " , * Host ) ) ;
CurlHeaders = curl_slist_append ( CurlHeaders , * FStringAnsi : : Printf ( " x-amz-content-sha256: %s " , * EmptyPayloadSha256 ) ) ;
CurlHeaders = curl_slist_append ( CurlHeaders , * FStringAnsi : : Printf ( " x-amz-date: %s " , * TimeString ) ) ;
CurlHeaders = curl_slist_append ( CurlHeaders , * GetAuthorizationHeader ( " GET " , UrlHostEnd , " " , CurlHeaders , * TimeString , * EmptyPayloadSha256 ) ) ;
// Create the callback data
FStringAnsi Domain ;
Domain . Append ( Url , UrlHostEnd ) ;
FCallbackData CallbackData ( Domain , OutResponseBody ) ;
// Setup the request
curl_easy_reset ( Curl ) ;
curl_easy_setopt ( Curl , CURLOPT_FOLLOWLOCATION , 1L ) ;
curl_easy_setopt ( Curl , CURLOPT_NOSIGNAL , 1L ) ;
curl_easy_setopt ( Curl , CURLOPT_HTTPGET , 1L ) ;
curl_easy_setopt ( Curl , CURLOPT_URL , Url ) ;
curl_easy_setopt ( Curl , CURLOPT_ACCEPT_ENCODING , " gzip " ) ;
# if S3DDC_HTTP_REQUEST_TIMOUT_ENABLED
curl_easy_setopt ( Curl , CURLOPT_CONNECTTIMEOUT , S3DDC_HTTP_REQUEST_TIMEOUT_SECONDS ) ;
# endif
// Headers
curl_easy_setopt ( Curl , CURLOPT_HTTPHEADER , CurlHeaders ) ;
curl_easy_setopt ( Curl , CURLOPT_HEADERDATA , CurlHeaders ) ;
// Progress
curl_easy_setopt ( Curl , CURLOPT_NOPROGRESS , 0 ) ;
curl_easy_setopt ( Curl , CURLOPT_XFERINFODATA , Callback ) ;
2021-04-28 16:22:18 -04:00
curl_easy_setopt ( Curl , CURLOPT_XFERINFOFUNCTION , & FHttpRequest : : StaticStatusFn ) ;
2020-03-12 13:29:21 -04:00
// Response
curl_easy_setopt ( Curl , CURLOPT_HEADERDATA , & CallbackData ) ;
2021-04-28 16:22:18 -04:00
curl_easy_setopt ( Curl , CURLOPT_HEADERFUNCTION , & FHttpRequest : : StaticWriteHeaderFn ) ;
2020-03-12 13:29:21 -04:00
curl_easy_setopt ( Curl , CURLOPT_WRITEDATA , & CallbackData ) ;
curl_easy_setopt ( Curl , CURLOPT_WRITEFUNCTION , StaticWriteBodyFn ) ;
// SSL options
curl_easy_setopt ( Curl , CURLOPT_USE_SSL , CURLUSESSL_ALL ) ;
curl_easy_setopt ( Curl , CURLOPT_SSL_VERIFYPEER , 1 ) ;
curl_easy_setopt ( Curl , CURLOPT_SSL_VERIFYHOST , 1 ) ;
curl_easy_setopt ( Curl , CURLOPT_SSLCERTTYPE , " PEM " ) ;
// SSL certification verification
curl_easy_setopt ( Curl , CURLOPT_CAINFO , nullptr ) ;
curl_easy_setopt ( Curl , CURLOPT_SSL_CTX_FUNCTION , * sslctx_function ) ;
curl_easy_setopt ( Curl , CURLOPT_SSL_CTX_DATA , & CallbackData ) ;
// Send the request
CURLcode CurlResult = curl_easy_perform ( Curl ) ;
// Free the headers object
curl_slist_free_all ( CurlHeaders ) ;
curl_easy_setopt ( Curl , CURLOPT_HEADERDATA , nullptr ) ;
// Get the response code
long ResponseCode = 0 ;
if ( CurlResult = = CURLE_OK )
{
CurlResult = curl_easy_getinfo ( Curl , CURLINFO_RESPONSE_CODE , & ResponseCode ) ;
}
if ( CurlResult ! = CURLE_OK )
{
2020-03-26 17:03:08 -04:00
if ( CurlResult ! = CURLE_ABORTED_BY_CALLBACK )
{
Log - > Logf ( ELogVerbosity : : Error , TEXT ( " Error while connecting to %s: %d (%s) " ) , ANSI_TO_TCHAR ( Url ) , CurlResult , ANSI_TO_TCHAR ( curl_easy_strerror ( CurlResult ) ) ) ;
}
2020-03-12 13:29:21 -04:00
return 500 ;
}
// Print any diagnostic output
if ( ! ( ResponseCode > = 200 & & ResponseCode < = 299 ) )
{
2021-12-15 17:23:24 -05:00
if ( FAnsiStringView ( Url ) . StartsWith ( " file:// " ) )
{
ResponseCode = 200 ;
}
else
{
Log - > Logf ( ELogVerbosity : : Error , TEXT ( " Download failed for %s (response %d): \n %s \n %s " ) , ANSI_TO_TCHAR ( Url ) , ResponseCode , * CallbackData . ResponseHeader . ToWideString ( ) , ANSI_TO_TCHAR ( ( const ANSICHAR * ) OutResponseBody . GetData ( ) ) ) ;
}
2020-03-12 13:29:21 -04:00
}
return ResponseCode ;
}
private :
struct FCallbackData
{
const FStringAnsi & Domain ;
FStringAnsi ResponseHeader ;
TArray < uint8 > & ResponseBody ;
FCallbackData ( const FStringAnsi & InDomain , TArray < uint8 > & InResponseBody )
: Domain ( InDomain )
, ResponseBody ( InResponseBody )
{
}
} ;
CURL * Curl ;
FStringAnsi Region ;
FStringAnsi AccessKey ;
FStringAnsi SecretKey ;
FStringAnsi GetAuthorizationHeader ( const ANSICHAR * Verb , const ANSICHAR * RelativeUrl , const ANSICHAR * QueryString , const curl_slist * Headers , const ANSICHAR * Timestamp , const ANSICHAR * Digest )
{
// Create the canonical list of headers
FStringAnsi CanonicalHeaders ;
for ( const curl_slist * Header = Headers ; Header ! = nullptr ; Header = Header - > next )
{
const ANSICHAR * Colon = FCStringAnsi : : Strchr ( Header - > data , ' : ' ) ;
if ( Colon ! = nullptr )
{
for ( const ANSICHAR * Char = Header - > data ; Char ! = Colon ; Char + + )
{
2021-11-10 13:08:00 -05:00
CanonicalHeaders . Append ( FCharAnsi : : ToLower ( * Char ) ) ;
2020-03-12 13:29:21 -04:00
}
CanonicalHeaders . Append ( ' : ' ) ;
const ANSICHAR * Value = Colon + 1 ;
while ( * Value = = ' ' )
{
Value + + ;
}
for ( ; * Value ! = 0 ; Value + + )
{
CanonicalHeaders . Append ( * Value ) ;
}
CanonicalHeaders . Append ( ' \n ' ) ;
}
}
// Create the list of signed headers
FStringAnsi SignedHeaders ;
for ( const curl_slist * Header = Headers ; Header ! = nullptr ; Header = Header - > next )
{
const ANSICHAR * Colon = FCStringAnsi : : Strchr ( Header - > data , ' : ' ) ;
if ( Colon ! = nullptr )
{
if ( SignedHeaders . Len ( ) > 0 )
{
SignedHeaders . Append ( ' ; ' ) ;
}
for ( const ANSICHAR * Char = Header - > data ; Char ! = Colon ; Char + + )
{
2021-11-10 13:08:00 -05:00
SignedHeaders . Append ( FCharAnsi : : ToLower ( * Char ) ) ;
2020-03-12 13:29:21 -04:00
}
}
}
// Build the canonical request string
FStringAnsi CanonicalRequest ;
CanonicalRequest . Append ( Verb ) ;
CanonicalRequest . Append ( ' \n ' ) ;
CanonicalRequest . Append ( RelativeUrl ) ;
CanonicalRequest . Append ( ' \n ' ) ;
CanonicalRequest . Append ( QueryString ) ;
CanonicalRequest . Append ( ' \n ' ) ;
CanonicalRequest . Append ( CanonicalHeaders ) ;
CanonicalRequest . Append ( ' \n ' ) ;
CanonicalRequest . Append ( SignedHeaders ) ;
CanonicalRequest . Append ( ' \n ' ) ;
CanonicalRequest . Append ( Digest ) ;
// Get the date
FStringAnsi DateString ;
for ( int32 Idx = 0 ; Timestamp [ Idx ] ! = 0 & & Timestamp [ Idx ] ! = ' T ' ; Idx + + )
{
DateString . Append ( Timestamp [ Idx ] ) ;
}
// Generate the signature key
FStringAnsi Key = FStringAnsi : : Printf ( " AWS4%s " , * SecretKey ) ;
FSHA256 DateHash = HmacSha256 ( DateString , ( const uint8 * ) * Key , Key . Len ( ) ) ;
FSHA256 RegionHash = HmacSha256 ( Region , DateHash . Digest , sizeof ( DateHash . Digest ) ) ;
FSHA256 ServiceHash = HmacSha256 ( " s3 " , RegionHash . Digest , sizeof ( RegionHash . Digest ) ) ;
FSHA256 SigningKeyHash = HmacSha256 ( " aws4_request " , ServiceHash . Digest , sizeof ( ServiceHash . Digest ) ) ;
// Calculate the signature
FStringAnsi DateRequest = FStringAnsi : : Printf ( " %s/%s/s3/aws4_request " , * DateString , * Region ) ;
FStringAnsi CanonicalRequestSha256 = Sha256 ( ( const uint8 * ) * CanonicalRequest , CanonicalRequest . Len ( ) ) . ToString ( ) ;
FStringAnsi StringToSign = FStringAnsi : : Printf ( " AWS4-HMAC-SHA256 \n %s \n %s \n %s " , Timestamp , * DateRequest , * CanonicalRequestSha256 ) ;
FStringAnsi Signature = HmacSha256 ( * StringToSign , SigningKeyHash . Digest , sizeof ( SigningKeyHash . Digest ) ) . ToString ( ) ;
// Format the final header
return FStringAnsi : : Printf ( " Authorization: AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s " , * AccessKey , * DateRequest , * SignedHeaders , * Signature ) ;
}
/**
* Returns the response buffer as a string . Note that is the request is performed
* with an external buffer as target buffer this string will be empty .
*/
static FString GetResponseAsString ( const TArray < uint8 > & Buffer )
{
FUTF8ToTCHAR TCHARData ( reinterpret_cast < const ANSICHAR * > ( Buffer . GetData ( ) ) , Buffer . Num ( ) ) ;
2023-11-13 14:15:15 -05:00
return FString : : ConstructFromPtrSize ( TCHARData . Get ( ) , TCHARData . Length ( ) ) ;
2020-03-12 13:29:21 -04:00
}
static int StaticStatusFn ( void * Ptr , curl_off_t TotalDownloadSize , curl_off_t CurrentDownloadSize , curl_off_t TotalUploadSize , curl_off_t CurrentUploadSize )
{
IRequestCallback * Callback = ( IRequestCallback * ) Ptr ;
if ( Callback ! = nullptr )
{
2021-11-10 13:08:00 -05:00
return Callback - > Update ( IntCastChecked < int > ( CurrentDownloadSize ) , IntCastChecked < int > ( TotalDownloadSize ) ) ? 0 : 1 ;
2020-03-12 13:29:21 -04:00
}
return 0 ;
}
static size_t StaticWriteHeaderFn ( void * Ptr , size_t SizeInBlocks , size_t BlockSizeInBytes , void * UserData )
{
const size_t WriteSize = SizeInBlocks * BlockSizeInBytes ;
if ( WriteSize > 0 )
{
FCallbackData * CallbackData = static_cast < FCallbackData * > ( UserData ) ;
CallbackData - > ResponseHeader . Append ( ( const ANSICHAR * ) Ptr , ( const ANSICHAR * ) Ptr + WriteSize ) ;
return WriteSize ;
}
return 0 ;
}
static size_t StaticWriteBodyFn ( void * Ptr , size_t SizeInBlocks , size_t BlockSizeInBytes , void * UserData )
{
const size_t WriteSize = SizeInBlocks * BlockSizeInBytes ;
if ( WriteSize > 0 )
{
FCallbackData * CallbackData = static_cast < FCallbackData * > ( UserData ) ;
// If this is the first part of the body being received, try to reserve
// memory if content length is defined in the header.
if ( CallbackData - > ResponseBody . Num ( ) = = 0 )
{
static const ANSICHAR Prefix [ ] = " Content-Length: " ;
static const size_t PrefixLen = UE_ARRAY_COUNT ( Prefix ) - 1 ;
for ( const ANSICHAR * Header = * CallbackData - > ResponseHeader ; ; Header + + )
{
// Check this header
if ( FCStringAnsi : : Strnicmp ( Header , Prefix , PrefixLen ) = = 0 )
{
size_t ContentLength = ( size_t ) atol ( Header + PrefixLen ) ;
if ( ContentLength > 0u & & ContentLength < S3DDC_MAX_BUFFER_RESERVE )
{
CallbackData - > ResponseBody . Reserve ( ContentLength ) ;
}
break ;
}
// Move to the next string
Header = FCStringAnsi : : Strchr ( Header , ' \n ' ) ;
if ( Header = = nullptr )
{
break ;
}
}
}
// Write to the target buffer
CallbackData - > ResponseBody . Append ( ( const uint8 * ) Ptr , WriteSize ) ;
return WriteSize ;
}
return 0 ;
}
static int SslCertVerify ( int PreverifyOk , X509_STORE_CTX * Context )
{
if ( PreverifyOk = = 1 )
{
SSL * Handle = static_cast < SSL * > ( X509_STORE_CTX_get_ex_data ( Context , SSL_get_ex_data_X509_STORE_CTX_idx ( ) ) ) ;
check ( Handle ) ;
SSL_CTX * SslContext = SSL_get_SSL_CTX ( Handle ) ;
check ( SslContext ) ;
FCallbackData * CallbackData = reinterpret_cast < FCallbackData * > ( SSL_CTX_get_app_data ( SslContext ) ) ;
check ( CallbackData ) ;
if ( ! FSslModule : : Get ( ) . GetCertificateManager ( ) . VerifySslCertificates ( Context , * CallbackData - > Domain ) )
{
PreverifyOk = 0 ;
}
}
return PreverifyOk ;
}
static CURLcode sslctx_function ( CURL * curl , void * sslctx , void * parm )
{
SSL_CTX * Context = static_cast < SSL_CTX * > ( sslctx ) ;
const ISslCertificateManager & CertificateManager = FSslModule : : Get ( ) . GetCertificateManager ( ) ;
CertificateManager . AddCertificatesToSslContext ( Context ) ;
SSL_CTX_set_verify ( Context , SSL_CTX_get_verify_mode ( Context ) , SslCertVerify ) ;
SSL_CTX_set_app_data ( Context , parm ) ;
/* all set to go */
return CURLE_OK ;
}
} ;
//----------------------------------------------------------------------------------------------------------
2022-02-14 14:43:39 -05:00
// FS3CacheStore::FRequestPool
2020-03-12 13:29:21 -04:00
//----------------------------------------------------------------------------------------------------------
2022-02-14 14:43:39 -05:00
class FS3CacheStore : : FRequestPool
2020-03-12 13:29:21 -04:00
{
public :
FRequestPool ( const ANSICHAR * Region , const ANSICHAR * AccessKey , const ANSICHAR * SecretKey )
{
Pool . SetNum ( S3DDC_REQUEST_POOL_SIZE ) ;
for ( uint8 i = 0 ; i < Pool . Num ( ) ; + + i )
{
Pool [ i ] . Usage = 0u ;
2021-04-28 16:22:18 -04:00
Pool [ i ] . Request = new FHttpRequest ( Region , AccessKey , SecretKey ) ;
2020-03-12 13:29:21 -04:00
}
}
~ FRequestPool ( )
{
for ( uint8 i = 0 ; i < Pool . Num ( ) ; + + i )
{
// No requests should be in use by now.
check ( Pool [ i ] . Usage . Load ( EMemoryOrder : : Relaxed ) = = 0u ) ;
delete Pool [ i ] . Request ;
}
}
long Download ( const TCHAR * Url , IRequestCallback * Callback , TArray < uint8 > & OutData , FOutputDevice * Log )
{
2021-04-28 16:22:18 -04:00
FHttpRequest * Request = WaitForFreeRequest ( ) ;
2020-03-12 13:29:21 -04:00
long ResponseCode = Request - > PerformBlocking ( TCHAR_TO_ANSI ( Url ) , Callback , OutData , Log ) ;
ReleaseRequestToPool ( Request ) ;
return ResponseCode ;
}
private :
struct FEntry
{
TAtomic < uint8 > Usage ;
2021-04-28 16:22:18 -04:00
FHttpRequest * Request ;
2020-03-12 13:29:21 -04:00
} ;
TArray < FEntry > Pool ;
2021-04-28 16:22:18 -04:00
FHttpRequest * WaitForFreeRequest ( )
2020-03-12 13:29:21 -04:00
{
TRACE_CPUPROFILER_EVENT_SCOPE ( S3DDC_WaitForConnPool ) ;
while ( true )
{
for ( uint8 i = 0 ; i < Pool . Num ( ) ; + + i )
{
if ( ! Pool [ i ] . Usage . Load ( EMemoryOrder : : Relaxed ) )
{
uint8 Expected = 0u ;
if ( Pool [ i ] . Usage . CompareExchange ( Expected , 1u ) )
{
return Pool [ i ] . Request ;
}
}
}
FPlatformProcess : : Sleep ( S3DDC_BACKEND_WAIT_INTERVAL ) ;
}
}
2021-04-28 16:22:18 -04:00
void ReleaseRequestToPool ( FHttpRequest * Request )
2020-03-12 13:29:21 -04:00
{
for ( uint8 i = 0 ; i < Pool . Num ( ) ; + + i )
{
if ( Pool [ i ] . Request = = Request )
{
uint8 Expected = 1u ;
Pool [ i ] . Usage . CompareExchange ( Expected , 0u ) ;
return ;
}
}
check ( false ) ;
}
} ;
//----------------------------------------------------------------------------------------------------------
2022-02-14 14:43:39 -05:00
// FS3CacheStore::FBundleEntry
2020-03-12 13:29:21 -04:00
//----------------------------------------------------------------------------------------------------------
2022-02-14 14:43:39 -05:00
struct FS3CacheStore : : FBundleEntry
2020-03-12 13:29:21 -04:00
{
int64 Offset ;
int32 Length ;
} ;
//----------------------------------------------------------------------------------------------------------
2022-02-14 14:43:39 -05:00
// FS3CacheStore::FBundle
2020-03-12 13:29:21 -04:00
//----------------------------------------------------------------------------------------------------------
2022-02-14 14:43:39 -05:00
struct FS3CacheStore : : FBundle
2020-03-12 13:29:21 -04:00
{
FString Name ;
FString ObjectKey ;
FString LocalFile ;
int32 CompressedLength ;
int32 UncompressedLength ;
TMap < FSHAHash , FBundleEntry > Entries ;
} ;
//----------------------------------------------------------------------------------------------------------
2022-02-14 14:43:39 -05:00
// FS3CacheStore::FBundleDownloadInfo
2020-03-12 13:29:21 -04:00
//----------------------------------------------------------------------------------------------------------
2022-02-14 14:43:39 -05:00
struct FS3CacheStore : : FBundleDownload final : IRequestCallback
2020-03-12 13:29:21 -04:00
{
FCriticalSection & CriticalSection ;
FBundle & Bundle ;
FString BundleUrl ;
FRequestPool & RequestPool ;
FFeedbackContext * Context ;
FGraphEventRef Event ;
int DownloadedBytes ;
FBundleDownload ( FCriticalSection & InCriticalSection , FBundle & InBundle , FString InBundleUrl , FRequestPool & InRequestPool , FFeedbackContext * InContext )
: CriticalSection ( InCriticalSection )
, Bundle ( InBundle )
, BundleUrl ( InBundleUrl )
, RequestPool ( InRequestPool )
, Context ( InContext )
, DownloadedBytes ( 0 )
{
}
void Execute ( )
{
if ( Context - > ReceivedUserCancel ( ) )
{
return ;
}
Context - > Logf ( TEXT ( " Downloading %s (%d bytes) " ) , * BundleUrl , Bundle . CompressedLength ) ;
TArray < uint8 > CompressedData ;
CompressedData . Reserve ( Bundle . CompressedLength ) ;
long ResponseCode = RequestPool . Download ( * BundleUrl , this , CompressedData , Context ) ;
if ( ! IsSuccessfulHttpResponse ( ResponseCode ) )
{
2020-03-26 17:03:08 -04:00
if ( ! Context - > ReceivedUserCancel ( ) )
{
Context - > Logf ( ELogVerbosity : : Warning , TEXT ( " Unable to download bundle %s (%d) " ) , * BundleUrl , ResponseCode ) ;
}
2020-03-12 13:29:21 -04:00
return ;
}
Context - > Logf ( TEXT ( " Decompressing %s (%d bytes) " ) , * BundleUrl , Bundle . UncompressedLength ) ;
TArray < uint8 > UncompressedData ;
UncompressedData . SetNum ( Bundle . UncompressedLength ) ;
if ( ! FCompression : : UncompressMemory ( NAME_Gzip , UncompressedData . GetData ( ) , Bundle . UncompressedLength , CompressedData . GetData ( ) , CompressedData . Num ( ) ) )
{
Context - > Logf ( ELogVerbosity : : Warning , TEXT ( " Unable to decompress bundle %s " ) , * BundleUrl ) ;
return ;
}
FString TempFile = Bundle . LocalFile + TEXT ( " .incoming " ) ;
if ( ! FFileHelper : : SaveArrayToFile ( UncompressedData , * TempFile ) )
{
Context - > Logf ( ELogVerbosity : : Warning , TEXT ( " Unable to save bundle to %s " ) , * TempFile ) ;
return ;
}
IFileManager : : Get ( ) . Move ( * Bundle . LocalFile , * TempFile ) ;
Context - > Logf ( TEXT ( " Finished downloading %s to %s " ) , * BundleUrl , * Bundle . LocalFile ) ;
}
2022-02-14 14:43:39 -05:00
bool Update ( int NumBytes , int MaxBytes ) final
2020-03-12 13:29:21 -04:00
{
FScopeLock Lock ( & CriticalSection ) ;
DownloadedBytes = NumBytes ;
return ! Context - > ReceivedUserCancel ( ) ;
}
} ;
2020-08-11 01:36:57 -04:00
//----------------------------------------------------------------------------------------------------------
2022-02-14 14:43:39 -05:00
// FS3CacheStore::FRootManifest
2020-08-11 01:36:57 -04:00
//----------------------------------------------------------------------------------------------------------
2022-02-14 14:43:39 -05:00
struct FS3CacheStore : : FRootManifest
2020-08-11 01:36:57 -04:00
{
FString AccessKey ;
FString SecretKey ;
TArray < FString > Keys ;
2021-03-31 17:59:50 -04:00
bool Load ( const FString & InRootManifestPath )
2020-08-11 01:36:57 -04:00
{
// Read the root manifest from disk
FString RootManifestText ;
2021-03-31 17:59:50 -04:00
if ( ! FFileHelper : : LoadFileToString ( RootManifestText , * InRootManifestPath ) )
2020-08-11 01:36:57 -04:00
{
2021-03-31 17:59:50 -04:00
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " Unable to read manifest from %s " ) , * InRootManifestPath ) ;
2020-08-11 01:36:57 -04:00
return false ;
}
// Deserialize a JSON object from the string
TSharedPtr < FJsonObject > RootManifestObject ;
if ( ! FJsonSerializer : : Deserialize ( TJsonReaderFactory < > : : Create ( RootManifestText ) , RootManifestObject ) | | ! RootManifestObject . IsValid ( ) )
{
2021-03-31 17:59:50 -04:00
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " Unable to parse manifest from %s " ) , * InRootManifestPath ) ;
2020-08-11 01:36:57 -04:00
return false ;
}
// Read the access and secret keys
if ( ! RootManifestObject - > TryGetStringField ( TEXT ( " AccessKey " ) , AccessKey ) )
{
2021-03-31 17:59:50 -04:00
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " Root manifest %s does not specify AccessKey " ) , * InRootManifestPath ) ;
2020-08-11 01:36:57 -04:00
return false ;
}
if ( ! RootManifestObject - > TryGetStringField ( TEXT ( " SecretKey " ) , SecretKey ) )
{
2021-03-31 17:59:50 -04:00
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " Root manifest %s does not specify SecretKey " ) , * InRootManifestPath ) ;
2020-08-11 01:36:57 -04:00
return false ;
}
// Parse out the list of manifests
const TArray < TSharedPtr < FJsonValue > > * RootEntriesArray ;
if ( ! RootManifestObject - > TryGetArrayField ( TEXT ( " Entries " ) , RootEntriesArray ) )
{
2021-03-31 17:59:50 -04:00
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " Root manifest from %s is missing entries array " ) , * InRootManifestPath ) ;
2020-08-11 01:36:57 -04:00
return false ;
}
for ( const TSharedPtr < FJsonValue > & Value : * RootEntriesArray )
{
const TSharedPtr < FJsonObject > & LastRootManifestEntry = ( * RootEntriesArray ) [ RootEntriesArray - > Num ( ) - 1 ] - > AsObject ( ) ;
2023-10-06 08:18:42 -04:00
Keys . Add ( LastRootManifestEntry - > GetStringField ( TEXT ( " Key " ) ) ) ;
2020-08-11 01:36:57 -04:00
}
return true ;
}
} ;
2020-03-12 13:29:21 -04:00
//----------------------------------------------------------------------------------------------------------
2022-02-14 14:43:39 -05:00
// FS3CacheStore
2020-03-12 13:29:21 -04:00
//----------------------------------------------------------------------------------------------------------
2023-06-21 15:23:06 -04:00
FS3CacheStore : : FS3CacheStore ( const TCHAR * InName , const TCHAR * InRootManifestPath , const TCHAR * InBaseUrl , const TCHAR * InRegion , const TCHAR * InCanaryObjectKey , const TCHAR * InCachePath , ICacheStoreOwner & InOwner )
2021-08-06 12:53:08 -04:00
: RootManifestPath ( InRootManifestPath )
2020-03-12 13:29:21 -04:00
, BaseUrl ( InBaseUrl )
, Region ( InRegion )
, CanaryObjectKey ( InCanaryObjectKey )
2020-03-27 15:57:11 -04:00
, CacheDir ( InCachePath )
2023-06-21 15:23:06 -04:00
, StoreOwner ( InOwner )
2020-03-12 13:29:21 -04:00
, bEnabled ( false )
{
2020-08-11 01:36:57 -04:00
FRootManifest RootManifest ;
if ( RootManifest . Load ( InRootManifestPath ) )
2020-03-26 17:59:17 -04:00
{
2020-08-11 01:36:57 -04:00
RequestPool . Reset ( new FRequestPool ( TCHAR_TO_ANSI ( InRegion ) , TCHAR_TO_ANSI ( * RootManifest . AccessKey ) , TCHAR_TO_ANSI ( * RootManifest . SecretKey ) ) ) ;
2020-03-26 17:59:17 -04:00
2021-12-15 17:23:24 -05:00
FString LocalManifestPath ;
if ( RootManifest . Keys . Last ( ) . StartsWith ( TEXT ( " file:// " ) ) )
{
LocalManifestPath = FPaths : : GetPath ( RootManifest . Keys . Last ( ) ) ;
}
2020-08-11 01:36:57 -04:00
// Test whether we can reach the canary URL
bool bCanaryValid = true ;
2021-12-15 17:23:24 -05:00
if ( LocalManifestPath . IsEmpty ( ) )
2020-03-26 17:59:17 -04:00
{
2021-12-15 17:23:24 -05:00
if ( GIsBuildMachine )
2020-03-12 13:29:21 -04:00
{
2021-12-15 17:23:24 -05:00
UE_LOG ( LogDerivedDataCache , Log , TEXT ( " S3DerivedDataBackend: Disabling on build machine " ) ) ;
2020-08-11 01:36:57 -04:00
bCanaryValid = false ;
2020-03-12 13:29:21 -04:00
}
2021-12-15 17:23:24 -05:00
else if ( CanaryObjectKey . Len ( ) > 0 )
{
TArray < uint8 > Data ;
FStringOutputDevice DummyOutputDevice ;
if ( ! IsSuccessfulHttpResponse ( RequestPool - > Download ( * ( BaseUrl / CanaryObjectKey ) , nullptr , Data , & DummyOutputDevice ) ) )
{
UE_LOG ( LogDerivedDataCache , Log , TEXT ( " S3DerivedDataBackend: Unable to download canary file. Disabling. " ) ) ;
bCanaryValid = false ;
}
}
2020-03-12 13:29:21 -04:00
}
2020-08-11 01:36:57 -04:00
// Allow the user to override it from the editor
bool bSetting ;
if ( GConfig - > GetBool ( TEXT ( " /Script/UnrealEd.EditorSettings " ) , TEXT ( " bEnableS3DDC " ) , bSetting , GEditorSettingsIni ) & & ! bSetting )
{
UE_LOG ( LogDerivedDataCache , Log , TEXT ( " S3DerivedDataBackend: Disabling due to config setting " ) ) ;
bCanaryValid = false ;
}
// Try to read the bundles
if ( bCanaryValid )
{
UE_LOG ( LogDerivedDataCache , Log , TEXT ( " Using %s S3 backend at %s " ) , * Region , * BaseUrl ) ;
FFeedbackContext * Context = FDesktopPlatformModule : : Get ( ) - > GetNativeFeedbackContext ( ) ;
Context - > BeginSlowTask ( NSLOCTEXT ( " S3DerivedDataBackend " , " DownloadingDDCBundles " , " Downloading DDC bundles... " ) , true , true ) ;
if ( DownloadManifest ( RootManifest , Context ) )
{
// Get the path for each bundle that needs downloading
for ( FBundle & Bundle : Bundles )
{
Bundle . LocalFile = CacheDir / Bundle . Name ;
}
// Remove any bundles that are no longer required
RemoveUnusedBundles ( ) ;
// Create a critical section used for updating download state
FCriticalSection CriticalSection ;
// Create all the download tasks
TArray < TSharedPtr < FBundleDownload > > Downloads ;
for ( FBundle & Bundle : Bundles )
{
if ( ! FPaths : : FileExists ( Bundle . LocalFile ) )
{
2021-12-15 17:23:24 -05:00
FString BundleUrl = LocalManifestPath . IsEmpty ( ) ? BaseUrl + Bundle . ObjectKey : LocalManifestPath / Bundle . Name + TEXT ( " .gz " ) ;
TSharedPtr < FBundleDownload > Download ( new FBundleDownload ( CriticalSection , Bundle , BundleUrl , * RequestPool . Get ( ) , Context ) ) ;
2020-08-11 01:36:57 -04:00
Download - > Event = FFunctionGraphTask : : CreateAndDispatchWhenReady ( [ Download ] ( ) { Download - > Execute ( ) ; } , TStatId ( ) ) ;
Downloads . Add ( MoveTemp ( Download ) ) ;
}
}
// Loop until the downloads have all finished
for ( bool bComplete = false ; ! bComplete ; )
{
FPlatformProcess : : Sleep ( 0.1f ) ;
int64 NumBytes = 0 ;
int64 MaxBytes = 0 ;
bComplete = true ;
CriticalSection . Lock ( ) ;
for ( TSharedPtr < FBundleDownload > & Download : Downloads )
{
NumBytes + = Download - > DownloadedBytes ;
MaxBytes + = Download - > Bundle . CompressedLength ;
bComplete & = Download - > Event - > IsComplete ( ) ;
}
CriticalSection . Unlock ( ) ;
int NumMB = ( int ) ( ( NumBytes + ( 1024 * 1024 ) - 1 ) / ( 1024 * 1024 ) ) ;
int MaxMB = ( int ) ( ( MaxBytes + ( 1024 * 1024 ) - 1 ) / ( 1024 * 1024 ) ) ;
if ( MaxBytes > 0 )
{
FText StatusText = FText : : Format ( NSLOCTEXT ( " S3DerivedDataBackend " , " DownloadingDDCBundlesPct " , " Downloading DDC bundles... ({0}Mb/{1}Mb) " ) , NumMB , MaxMB ) ;
Context - > StatusUpdate ( ( int ) ( ( NumBytes * 1000 ) / MaxBytes ) , 1000 , StatusText ) ;
}
}
// Mount all the bundles
ParallelFor ( Bundles . Num ( ) , [ this ] ( int32 Index ) { ReadBundle ( Bundles [ Index ] ) ; } ) ;
bEnabled = true ;
2023-06-21 15:23:06 -04:00
constexpr ECacheStoreFlags Flags = ECacheStoreFlags : : Local | ECacheStoreFlags : : Query | ECacheStoreFlags : : StopStore ;
StoreOwner . Add ( this , Flags ) ;
StoreStats = StoreOwner . CreateStats ( this , Flags , TEXTVIEW ( " S3 " ) , InName , BaseUrl ) ;
2020-08-11 01:36:57 -04:00
}
Context - > EndSlowTask ( ) ;
}
2020-03-12 13:29:21 -04:00
}
}
2023-06-21 15:23:06 -04:00
FS3CacheStore : : ~ FS3CacheStore ( )
{
if ( StoreStats )
{
StoreOwner . DestroyStats ( StoreStats ) ;
}
}
2022-06-07 11:17:10 -04:00
void FS3CacheStore : : LegacyStats ( FDerivedDataCacheStatsNode & OutNode )
2020-03-12 13:29:21 -04:00
{
2023-06-21 15:23:06 -04:00
checkNoEntry ( ) ;
2020-03-24 19:12:36 -04:00
}
2022-02-14 14:43:39 -05:00
bool FS3CacheStore : : DownloadManifest ( const FRootManifest & RootManifest , FFeedbackContext * Context )
2020-03-12 13:29:21 -04:00
{
2020-03-12 17:36:41 -04:00
// Read the root manifest from disk
2020-08-11 01:36:57 -04:00
if ( RootManifest . Keys . Num ( ) = = 0 )
2020-03-12 13:29:21 -04:00
{
2020-08-11 01:36:57 -04:00
Context - > Logf ( ELogVerbosity : : Warning , TEXT ( " Root manifest has empty entries array " ) ) ;
2020-03-12 17:36:41 -04:00
return false ;
}
// Get the object key for the last entry
2020-08-11 01:36:57 -04:00
FString BundleManifestKey = RootManifest . Keys . Last ( ) ;
2020-03-12 17:36:41 -04:00
// Download the bundle manifest
2021-12-15 17:23:24 -05:00
FString BundleManifestUrl = BundleManifestKey . StartsWith ( TEXT ( " file:// " ) ) ? BundleManifestKey : BaseUrl + BundleManifestKey ;
2020-03-12 17:36:41 -04:00
TArray < uint8 > BundleManifestData ;
2021-12-15 17:23:24 -05:00
long ResponseCode = RequestPool - > Download ( * BundleManifestUrl , nullptr , BundleManifestData , Context ) ;
2020-03-12 17:36:41 -04:00
if ( ! IsSuccessfulHttpResponse ( ResponseCode ) )
{
Context - > Logf ( ELogVerbosity : : Warning , TEXT ( " Unable to download bundle manifest from %s (%d) " ) , * BundleManifestKey , ( int ) ResponseCode ) ;
return false ;
}
// Convert it to text
BundleManifestData . Add ( 0 ) ;
FString BundleManifestText = ANSI_TO_TCHAR ( ( const ANSICHAR * ) BundleManifestData . GetData ( ) ) ;
// Deserialize a JSON object from the string
TSharedPtr < FJsonObject > BundleManifestObject ;
if ( ! FJsonSerializer : : Deserialize ( TJsonReaderFactory < > : : Create ( BundleManifestText ) , BundleManifestObject ) | | ! BundleManifestObject . IsValid ( ) )
{
Context - > Logf ( ELogVerbosity : : Warning , TEXT ( " Unable to parse manifest from %s " ) , * BundleManifestKey ) ;
2020-03-12 13:29:21 -04:00
return false ;
}
// Parse out the list of bundles
const TArray < TSharedPtr < FJsonValue > > * BundlesArray ;
2020-03-12 17:36:41 -04:00
if ( ! BundleManifestObject - > TryGetArrayField ( TEXT ( " Entries " ) , BundlesArray ) )
2020-03-12 13:29:21 -04:00
{
2020-03-12 17:36:41 -04:00
Context - > Logf ( ELogVerbosity : : Warning , TEXT ( " Manifest from %s is missing bundles array " ) , * BundleManifestKey ) ;
2020-03-12 13:29:21 -04:00
return false ;
}
// Parse out each bundle
for ( const TSharedPtr < FJsonValue > & BundleValue : * BundlesArray )
{
const FJsonObject & BundleObject = * BundleValue - > AsObject ( ) ;
FBundle Bundle ;
if ( ! BundleObject . TryGetStringField ( TEXT ( " Name " ) , Bundle . Name ) )
{
2020-03-12 17:36:41 -04:00
Context - > Logf ( ELogVerbosity : : Warning , TEXT ( " Manifest from %s is missing a bundle name " ) , * BundleManifestKey ) ;
2020-03-12 13:29:21 -04:00
return false ;
}
if ( ! BundleObject . TryGetStringField ( TEXT ( " ObjectKey " ) , Bundle . ObjectKey ) )
{
2020-03-12 17:36:41 -04:00
Context - > Logf ( ELogVerbosity : : Warning , TEXT ( " Manifest from %s is missing an bundle object key " ) , * BundleManifestKey ) ;
2020-03-12 13:29:21 -04:00
return false ;
}
if ( ! BundleObject . TryGetNumberField ( TEXT ( " CompressedLength " ) , Bundle . CompressedLength ) )
{
2020-03-12 17:36:41 -04:00
Context - > Logf ( ELogVerbosity : : Warning , TEXT ( " Manifest from %s is missing the compressed length " ) , * BundleManifestKey ) ;
2020-03-12 13:29:21 -04:00
return false ;
}
if ( ! BundleObject . TryGetNumberField ( TEXT ( " UncompressedLength " ) , Bundle . UncompressedLength ) )
{
2020-03-12 17:36:41 -04:00
Context - > Logf ( ELogVerbosity : : Warning , TEXT ( " Manifest from %s is missing the uncompressed length " ) , * BundleManifestKey ) ;
2020-03-12 13:29:21 -04:00
return false ;
}
Bundles . Add ( MoveTemp ( Bundle ) ) ;
}
return true ;
}
2022-02-14 14:43:39 -05:00
void FS3CacheStore : : RemoveUnusedBundles ( )
2020-03-12 13:29:21 -04:00
{
IFileManager & FileManager = IFileManager : : Get ( ) ;
// Find all the files we want to keep
TSet < FString > KeepFiles ;
for ( const FBundle & Bundle : Bundles )
{
KeepFiles . Add ( Bundle . Name ) ;
}
// Find all the files on disk
TArray < FString > Files ;
FileManager . FindFiles ( Files , * CacheDir ) ;
// Remove anything left over
for ( const FString & File : Files )
{
if ( ! KeepFiles . Contains ( File ) )
{
FileManager . Delete ( * ( CacheDir / File ) ) ;
}
}
}
2022-02-14 14:43:39 -05:00
void FS3CacheStore : : ReadBundle ( FBundle & Bundle )
2020-03-12 13:29:21 -04:00
{
IFileManager & FileManager = IFileManager : : Get ( ) ;
// Open the file for reading. If this fails, assume it's because the download was aborted.
TUniquePtr < FArchive > Reader ( FileManager . CreateFileReader ( * Bundle . LocalFile ) ) ;
if ( ! Reader . IsValid ( ) | | Reader - > IsError ( ) )
{
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " Unable to open bundle %s for reading. Ignoring. " ) , * Bundle . LocalFile ) ;
return ;
}
struct FFileHeader
{
uint32 Signature ;
int32 NumRecords ;
} ;
FFileHeader Header ;
Reader - > Serialize ( & Header , sizeof ( Header ) ) ;
const uint32 BundleSignature = ( uint32 ) ' D ' | ( ( uint32 ) ' D ' < < 8 ) | ( ( uint32 ) ' B ' < < 16 ) ;
const uint32 BundleSignatureV1 = BundleSignature | ( 1U < < 24 ) ;
if ( Header . Signature ! = BundleSignatureV1 )
{
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " Unable to read bundle with signature %08x " ) , Header . Signature ) ;
return ;
}
struct FFileRecord
{
FSHAHash Hash ;
uint32 Length ;
} ;
TArray < FFileRecord > Records ;
Records . SetNum ( Header . NumRecords ) ;
Reader - > Serialize ( Records . GetData ( ) , Header . NumRecords * sizeof ( FFileRecord ) ) ;
Bundle . Entries . Reserve ( Records . Num ( ) ) ;
int64 Offset = Reader - > Tell ( ) ;
for ( const FFileRecord & Record : Records )
{
FBundleEntry & Entry = Bundle . Entries . Add ( Record . Hash ) ;
Entry . Offset = Offset ;
Entry . Length = Record . Length ;
Offset + = Record . Length ;
check ( Offset < = Bundle . UncompressedLength ) ;
}
}
2022-06-07 11:17:10 -04:00
bool FS3CacheStore : : LegacyDebugOptions ( FBackendDebugOptions & InOptions )
2020-03-26 20:55:39 -04:00
{
DebugOptions = InOptions ;
return true ;
}
2022-02-14 14:43:39 -05:00
FOptionalCacheRecord FS3CacheStore : : GetCacheRecordOnly (
2022-02-11 12:57:03 -05:00
const FStringView Name ,
const FCacheKey & Key ,
2023-06-21 15:23:06 -04:00
const FCacheRecordPolicy & Policy ,
FRequestStats & Stats )
2022-02-11 12:57:03 -05:00
{
if ( ! IsUsable ( ) )
{
UE_LOG ( LogDerivedDataCache , VeryVerbose ,
TEXT ( " %s: Skipped get of %s from '%.*s' because this cache store is not available " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return FOptionalCacheRecord ( ) ;
}
// Skip the request if querying the cache is disabled.
if ( ! EnumHasAnyFlags ( Policy . GetRecordPolicy ( ) , ECachePolicy : : QueryLocal ) )
{
UE_LOG ( LogDerivedDataCache , VeryVerbose , TEXT ( " %s: Skipped get of %s from '%.*s' due to cache policy " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return FOptionalCacheRecord ( ) ;
}
2022-03-31 10:06:47 -04:00
if ( DebugOptions . ShouldSimulateGetMiss ( Key ) )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Simulated miss for get of %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return FOptionalCacheRecord ( ) ;
}
2022-02-11 12:57:03 -05:00
TStringBuilder < 256 > Path ;
BuildCachePackagePath ( Key , Path ) ;
FOptionalCacheRecord Record ;
{
FCbPackage Package ;
2023-06-21 15:23:06 -04:00
if ( ! LoadFileWithHash ( Path , Name , Stats , [ & Package ] ( FArchive & Ar ) { Package . TryLoad ( Ar ) ; } ) )
2022-02-11 12:57:03 -05:00
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache miss with missing package for %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return Record ;
}
if ( ValidateCompactBinary ( Package , ECbValidateMode : : Default | ECbValidateMode : : Package ) ! = ECbValidateError : : None )
{
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: Cache miss with invalid package for %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return Record ;
}
Record = FCacheRecord : : Load ( Package ) ;
if ( Record . IsNull ( ) )
{
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: Cache miss with record load failure for %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return Record ;
}
}
return Record ;
}
2022-02-14 14:43:39 -05:00
FOptionalCacheRecord FS3CacheStore : : GetCacheRecord (
2022-02-11 12:57:03 -05:00
const FStringView Name ,
const FCacheKey & Key ,
const FCacheRecordPolicy & Policy ,
2023-06-21 15:23:06 -04:00
EStatus & OutStatus ,
FRequestStats & Stats )
2022-02-11 12:57:03 -05:00
{
2023-06-21 15:23:06 -04:00
FOptionalCacheRecord Record = GetCacheRecordOnly ( Name , Key , Policy , Stats ) ;
2022-02-11 12:57:03 -05:00
if ( Record . IsNull ( ) )
{
OutStatus = EStatus : : Error ;
return Record ;
}
OutStatus = EStatus : : Ok ;
FCacheRecordBuilder RecordBuilder ( Key ) ;
const ECachePolicy RecordPolicy = Policy . GetRecordPolicy ( ) ;
if ( ! EnumHasAnyFlags ( RecordPolicy , ECachePolicy : : SkipMeta ) )
{
RecordBuilder . SetMeta ( FCbObject ( Record . Get ( ) . GetMeta ( ) ) ) ;
}
for ( const FValueWithId & Value : Record . Get ( ) . GetValues ( ) )
{
const FValueId & Id = Value . GetId ( ) ;
const ECachePolicy ValuePolicy = Policy . GetValuePolicy ( Id ) ;
FValue Content ;
2023-06-21 15:23:06 -04:00
if ( GetCacheContent ( Name , Key , Id , Value , ValuePolicy , Content , Stats ) )
2022-02-11 12:57:03 -05:00
{
RecordBuilder . AddValue ( Id , MoveTemp ( Content ) ) ;
}
else if ( EnumHasAnyFlags ( RecordPolicy , ECachePolicy : : PartialRecord ) )
{
OutStatus = EStatus : : Error ;
RecordBuilder . AddValue ( Value ) ;
}
else
{
OutStatus = EStatus : : Error ;
return FOptionalCacheRecord ( ) ;
}
}
return RecordBuilder . Build ( ) ;
}
2022-02-14 14:43:39 -05:00
bool FS3CacheStore : : GetCacheValueOnly (
2022-02-11 12:57:03 -05:00
const FStringView Name ,
const FCacheKey & Key ,
const ECachePolicy Policy ,
2023-06-21 15:23:06 -04:00
FValue & OutValue ,
FRequestStats & Stats )
2022-02-11 12:57:03 -05:00
{
if ( ! IsUsable ( ) )
{
UE_LOG ( LogDerivedDataCache , VeryVerbose ,
TEXT ( " %s: Skipped get of %s from '%.*s' because this cache store is not available " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
// Skip the request if querying the cache is disabled.
if ( ! EnumHasAnyFlags ( Policy , ECachePolicy : : QueryLocal ) )
{
UE_LOG ( LogDerivedDataCache , VeryVerbose , TEXT ( " %s: Skipped get of %s from '%.*s' due to cache policy " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
2022-03-31 10:06:47 -04:00
if ( DebugOptions . ShouldSimulateGetMiss ( Key ) )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Simulated miss for get of %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
2022-02-11 12:57:03 -05:00
TStringBuilder < 256 > Path ;
BuildCachePackagePath ( Key , Path ) ;
FCbPackage Package ;
2023-06-21 15:23:06 -04:00
if ( ! LoadFileWithHash ( Path , Name , Stats , [ & Package ] ( FArchive & Ar ) { Package . TryLoad ( Ar ) ; } ) )
2022-02-11 12:57:03 -05:00
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache miss with missing package for %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
if ( ValidateCompactBinary ( Package , ECbValidateMode : : Default | ECbValidateMode : : Package ) ! = ECbValidateError : : None )
{
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: Cache miss with invalid package for %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
const FCbObjectView Object = Package . GetObject ( ) ;
const FIoHash RawHash = Object [ " RawHash " ] . AsHash ( ) ;
const uint64 RawSize = Object [ " RawSize " ] . AsUInt64 ( MAX_uint64 ) ;
if ( RawHash . IsZero ( ) | | RawSize = = MAX_uint64 )
{
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " %s: Cache miss with invalid value for %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
if ( const FCbAttachment * const Attachment = Package . FindAttachment ( RawHash ) )
{
const FCompressedBuffer & Data = Attachment - > AsCompressedBinary ( ) ;
if ( Data . GetRawHash ( ) ! = RawHash | | Data . GetRawSize ( ) ! = RawSize )
{
UE_LOG ( LogDerivedDataCache , Display ,
TEXT ( " %s: Cache miss with invalid value attachment for %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
OutValue = FValue ( Data ) ;
}
else
{
OutValue = FValue ( RawHash , RawSize ) ;
}
return true ;
}
2022-02-14 14:43:39 -05:00
bool FS3CacheStore : : GetCacheValue (
2022-02-11 12:57:03 -05:00
const FStringView Name ,
const FCacheKey & Key ,
const ECachePolicy Policy ,
2023-06-21 15:23:06 -04:00
FValue & OutValue ,
FRequestStats & Stats )
2022-02-11 12:57:03 -05:00
{
2023-06-21 15:23:06 -04:00
return GetCacheValueOnly ( Name , Key , Policy , OutValue , Stats ) & & GetCacheContent ( Name , Key , { } , OutValue , Policy , OutValue , Stats ) ;
2022-02-11 12:57:03 -05:00
}
2023-06-21 15:23:06 -04:00
bool FS3CacheStore : : GetCacheContentExists ( const FCacheKey & Key , const FIoHash & RawHash , FRequestStats & Stats ) const
2022-02-11 12:57:03 -05:00
{
TStringBuilder < 256 > Path ;
BuildCacheContentPath ( RawHash , Path ) ;
2023-06-21 15:23:06 -04:00
return FileExists ( Path , Stats ) ;
2022-02-11 12:57:03 -05:00
}
2022-02-14 14:43:39 -05:00
bool FS3CacheStore : : GetCacheContent (
2022-02-11 12:57:03 -05:00
const FStringView Name ,
const FCacheKey & Key ,
const FValueId & Id ,
const FValue & Value ,
const ECachePolicy Policy ,
2023-06-21 15:23:06 -04:00
FValue & OutValue ,
FRequestStats & Stats ) const
2022-02-11 12:57:03 -05:00
{
if ( ! EnumHasAnyFlags ( Policy , ECachePolicy : : Query ) )
{
OutValue = Value . RemoveData ( ) ;
return true ;
}
if ( Value . HasData ( ) )
{
OutValue = EnumHasAnyFlags ( Policy , ECachePolicy : : SkipData ) ? Value . RemoveData ( ) : Value ;
return true ;
}
const FIoHash & RawHash = Value . GetRawHash ( ) ;
TStringBuilder < 256 > Path ;
BuildCacheContentPath ( RawHash , Path ) ;
if ( EnumHasAnyFlags ( Policy , ECachePolicy : : SkipData ) )
{
2023-06-21 15:23:06 -04:00
if ( FileExists ( Path , Stats ) )
2022-02-11 12:57:03 -05:00
{
OutValue = Value ;
return true ;
}
}
else
{
FCompressedBuffer CompressedBuffer ;
2023-06-21 15:23:06 -04:00
if ( LoadFileWithHash ( Path , Name , Stats , [ & CompressedBuffer ] ( FArchive & Ar ) { CompressedBuffer = FCompressedBuffer : : Load ( Ar ) ; } ) )
2022-02-11 12:57:03 -05:00
{
if ( CompressedBuffer . GetRawHash ( ) = = RawHash )
{
OutValue = FValue ( MoveTemp ( CompressedBuffer ) ) ;
return true ;
}
UE_LOG ( LogDerivedDataCache , Display ,
TEXT ( " %s: Cache miss with corrupted value %s with hash %s for %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 16 > ( Id ) , * WriteToString < 48 > ( RawHash ) ,
* WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
}
UE_LOG ( LogDerivedDataCache , Verbose ,
TEXT ( " %s: Cache miss with missing value %s with hash %s for %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 16 > ( Id ) , * WriteToString < 48 > ( RawHash ) , * WriteToString < 96 > ( Key ) ,
Name . Len ( ) , Name . GetData ( ) ) ;
return false ;
}
2022-02-14 14:43:39 -05:00
void FS3CacheStore : : GetCacheContent (
2022-02-11 12:57:03 -05:00
const FStringView Name ,
const FCacheKey & Key ,
const FValueId & Id ,
const FValue & Value ,
const ECachePolicy Policy ,
FCompressedBufferReader & Reader ,
2023-06-21 15:23:06 -04:00
TUniquePtr < FArchive > & OutArchive ,
FRequestStats & Stats ) const
2022-02-11 12:57:03 -05:00
{
2023-06-21 15:23:06 -04:00
class FStatsArchive final : TUniquePtr < FArchive > , public FArchiveProxy
{
public :
FStatsArchive ( FArchive & InArchive , FRequestStats & InStats )
: TUniquePtr < FArchive > ( & InArchive )
, FArchiveProxy ( InArchive )
, Stats ( InStats )
{
}
void Serialize ( void * V , int64 Length ) final
{
Stats . PhysicalReadSize + = uint64 ( Length ) ;
FArchiveProxy : : Serialize ( V , Length ) ;
}
private :
FRequestStats & Stats ;
} ;
2022-02-11 12:57:03 -05:00
if ( ! EnumHasAnyFlags ( Policy , ECachePolicy : : Query ) )
{
return ;
}
if ( Value . HasData ( ) )
{
if ( ! EnumHasAnyFlags ( Policy , ECachePolicy : : SkipData ) )
{
Reader . SetSource ( Value . GetData ( ) ) ;
}
OutArchive . Reset ( ) ;
return ;
}
const FIoHash & RawHash = Value . GetRawHash ( ) ;
TStringBuilder < 256 > Path ;
BuildCacheContentPath ( RawHash , Path ) ;
if ( EnumHasAllFlags ( Policy , ECachePolicy : : SkipData ) )
{
2023-06-21 15:23:06 -04:00
if ( FileExists ( Path , Stats ) )
2022-02-11 12:57:03 -05:00
{
return ;
}
}
else
{
2023-06-21 15:23:06 -04:00
OutArchive = OpenFile ( Path , Name , Stats ) ;
2022-02-11 12:57:03 -05:00
if ( OutArchive )
{
2023-06-21 15:23:06 -04:00
OutArchive . Reset ( new FStatsArchive ( * OutArchive . Release ( ) , Stats ) ) ;
UE_LOG ( LogDerivedDataCache , VeryVerbose ,
TEXT ( " %s: Opened %s from '%.*s' " ) ,
* GetName ( ) , * Path , Name . Len ( ) , Name . GetData ( ) ) ;
2022-02-11 12:57:03 -05:00
Reader . SetSource ( * OutArchive ) ;
if ( Reader . GetRawHash ( ) = = RawHash )
{
return ;
}
UE_LOG ( LogDerivedDataCache , Display ,
TEXT ( " %s: Cache miss with corrupted value %s with hash %s for %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 16 > ( Id ) , * WriteToString < 48 > ( RawHash ) ,
* WriteToString < 96 > ( Key ) , Name . Len ( ) , Name . GetData ( ) ) ;
Reader . ResetSource ( ) ;
OutArchive . Reset ( ) ;
return ;
}
}
UE_LOG ( LogDerivedDataCache , Verbose ,
TEXT ( " %s: Cache miss with missing value %s with hash %s for %s from '%.*s' " ) ,
* GetName ( ) , * WriteToString < 16 > ( Id ) , * WriteToString < 48 > ( RawHash ) , * WriteToString < 96 > ( Key ) ,
Name . Len ( ) , Name . GetData ( ) ) ;
}
2022-02-14 14:43:39 -05:00
void FS3CacheStore : : BuildCachePackagePath ( const FCacheKey & CacheKey , FStringBuilderBase & Path ) const
2022-02-11 12:57:03 -05:00
{
BuildPathForCachePackage ( CacheKey , Path ) ;
}
2022-02-14 14:43:39 -05:00
void FS3CacheStore : : BuildCacheContentPath ( const FIoHash & RawHash , FStringBuilderBase & Path ) const
2022-02-11 12:57:03 -05:00
{
BuildPathForCacheContent ( RawHash , Path ) ;
}
2022-02-14 14:43:39 -05:00
bool FS3CacheStore : : LoadFileWithHash (
2022-02-11 12:57:03 -05:00
FStringBuilderBase & Path ,
const FStringView DebugName ,
2023-06-21 15:23:06 -04:00
FRequestStats & Stats ,
2022-02-11 12:57:03 -05:00
const TFunctionRef < void ( FArchive & Ar ) > ReadFunction ) const
{
2023-06-21 15:23:06 -04:00
return LoadFile ( Path , DebugName , Stats , [ this , & Path , & DebugName , & ReadFunction ] ( FArchive & Ar )
2022-02-11 12:57:03 -05:00
{
THashingArchiveProxy < FBlake3 > HashAr ( Ar ) ;
ReadFunction ( HashAr ) ;
const FBlake3Hash Hash = HashAr . GetHash ( ) ;
FBlake3Hash SavedHash ;
Ar < < SavedHash ;
if ( Hash ! = SavedHash & & ! Ar . IsError ( ) )
{
Ar . SetError ( ) ;
UE_LOG ( LogDerivedDataCache , Display ,
TEXT ( " %s: File %s from '%.*s' is corrupted and has hash %s when %s is expected. " ) ,
* GetName ( ) , * Path , DebugName . Len ( ) , DebugName . GetData ( ) ,
* WriteToString < 80 > ( Hash ) , * WriteToString < 80 > ( SavedHash ) ) ;
}
} ) ;
}
2022-02-14 14:43:39 -05:00
bool FS3CacheStore : : LoadFile (
2022-02-11 12:57:03 -05:00
FStringBuilderBase & Path ,
const FStringView DebugName ,
2023-06-21 15:23:06 -04:00
FRequestStats & Stats ,
2022-02-11 12:57:03 -05:00
const TFunctionRef < void ( FArchive & Ar ) > ReadFunction ) const
{
check ( IsUsable ( ) ) ;
const double StartTime = FPlatformTime : : Seconds ( ) ;
int64 ReadSize = 0 ;
bool bError = false ;
2023-06-21 15:23:06 -04:00
if ( TUniquePtr < FArchive > Ar = OpenFile ( Path , DebugName , Stats ) )
2022-02-11 12:57:03 -05:00
{
int64 ReadStart = Ar - > Tell ( ) ;
ReadFunction ( * Ar ) ;
ReadSize = Ar - > Tell ( ) - ReadStart ;
bError = ! Ar - > Close ( ) ;
if ( bError )
{
UE_LOG ( LogDerivedDataCache , Display ,
TEXT ( " %s: Failed to load file %s from '%.*s'. " ) ,
* GetName ( ) , * Path , DebugName . Len ( ) , DebugName . GetData ( ) ) ;
}
}
const double ReadDuration = FPlatformTime : : Seconds ( ) - StartTime ;
2022-09-08 13:36:12 -04:00
const double ReadSpeed = ReadDuration > 0.001 ? ( ( double ) ReadSize / ReadDuration ) / ( 1024.0 * 1024.0 ) : 0.0 ;
2022-02-11 12:57:03 -05:00
UE_LOG ( LogDerivedDataCache , VeryVerbose ,
TEXT ( " %s: Loaded %s from '%.*s' (% " INT64_FMT " bytes, %.02f secs, %.2f MiB/s) " ) ,
* GetName ( ) , * Path , DebugName . Len ( ) , DebugName . GetData ( ) , ReadSize , ReadDuration , ReadSpeed ) ;
2023-06-21 15:23:06 -04:00
if ( ReadSize > 0 )
{
Stats . PhysicalReadSize + = uint64 ( ReadSize ) ;
}
2022-02-11 12:57:03 -05:00
return ! bError & & ReadSize > 0 ;
}
2023-06-21 15:23:06 -04:00
TUniquePtr < FArchive > FS3CacheStore : : OpenFile ( FStringBuilderBase & Path , const FStringView DebugName , FRequestStats & Stats ) const
2022-02-11 12:57:03 -05:00
{
2023-06-21 15:23:06 -04:00
const FMonotonicTimePoint StartTime = FMonotonicTimePoint : : Now ( ) ;
ON_SCOPE_EXIT { Stats . AddLatency ( FMonotonicTimePoint : : Now ( ) - StartTime ) ; } ;
2022-02-11 12:57:03 -05:00
FSHAHash Hash ;
auto AnsiString = StringCast < ANSICHAR > ( * FString ( Path . ToString ( ) ) . ToUpper ( ) ) ;
FSHA1 : : HashBuffer ( AnsiString . Get ( ) , AnsiString . Length ( ) , Hash . Hash ) ;
for ( const FBundle & Bundle : Bundles )
{
const FBundleEntry * Entry = Bundle . Entries . Find ( Hash ) ;
if ( Entry ! = nullptr )
{
TUniquePtr < FArchive > Reader ( IFileManager : : Get ( ) . CreateFileReader ( * Bundle . LocalFile ) ) ;
if ( Reader . IsValid ( ) & & ! Reader - > IsError ( ) )
{
Reader - > Seek ( Entry - > Offset ) ;
return Reader ;
}
}
}
return nullptr ;
}
2023-06-21 15:23:06 -04:00
bool FS3CacheStore : : FileExists ( FStringBuilderBase & Path , FRequestStats & Stats ) const
2022-02-11 12:57:03 -05:00
{
2023-06-21 15:23:06 -04:00
const FMonotonicTimePoint StartTime = FMonotonicTimePoint : : Now ( ) ;
ON_SCOPE_EXIT { Stats . AddLatency ( FMonotonicTimePoint : : Now ( ) - StartTime ) ; } ;
2022-02-11 12:57:03 -05:00
FSHAHash Hash ;
auto AnsiString = StringCast < ANSICHAR > ( * FString ( Path . ToString ( ) ) . ToUpper ( ) ) ;
FSHA1 : : HashBuffer ( AnsiString . Get ( ) , AnsiString . Length ( ) , Hash . Hash ) ;
for ( const FBundle & Bundle : Bundles )
{
const FBundleEntry * Entry = Bundle . Entries . Find ( Hash ) ;
if ( Entry ! = nullptr )
{
return IFileManager : : Get ( ) . FileExists ( * Bundle . LocalFile ) ;
}
}
return false ;
}
2022-02-14 14:43:39 -05:00
void FS3CacheStore : : Put (
2021-12-13 13:32:28 -05:00
const TConstArrayView < FCachePutRequest > Requests ,
2021-08-04 18:08:50 -04:00
IRequestOwner & Owner ,
2021-04-28 16:22:18 -04:00
FOnCachePutComplete & & OnComplete )
{
2022-02-14 14:43:39 -05:00
CompleteWithStatus ( Requests , OnComplete , EStatus : : Error ) ;
2021-04-28 16:22:18 -04:00
}
2022-02-14 14:43:39 -05:00
void FS3CacheStore : : Get (
2021-12-13 13:32:28 -05:00
const TConstArrayView < FCacheGetRequest > Requests ,
2021-08-04 18:08:50 -04:00
IRequestOwner & Owner ,
2021-04-28 16:22:18 -04:00
FOnCacheGetComplete & & OnComplete )
{
2022-02-11 12:57:03 -05:00
for ( const FCacheGetRequest & Request : Requests )
2021-04-28 16:22:18 -04:00
{
2022-03-17 09:07:20 -04:00
EStatus Status = EStatus : : Error ;
FOptionalCacheRecord Record ;
2023-06-21 15:23:06 -04:00
FRequestStats RequestStats ;
2023-06-23 10:51:52 -04:00
RequestStats . Name = Request . Name ;
2023-06-21 15:23:06 -04:00
RequestStats . Bucket = Request . Key . Bucket ;
RequestStats . Type = ERequestType : : Record ;
RequestStats . Op = ERequestOp : : Get ;
2021-04-28 16:22:18 -04:00
{
2022-03-17 09:07:20 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( S3DDC_Get ) ;
TRACE_COUNTER_INCREMENT ( S3DDC_Get ) ;
2023-06-21 15:23:06 -04:00
FRequestTimer RequestTimer ( RequestStats ) ;
if ( ( Record = GetCacheRecord ( Request . Name , Request . Key , Request . Policy , Status , RequestStats ) ) )
2022-03-17 09:07:20 -04:00
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache hit for %s from '%s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Request . Key ) , * Request . Name ) ;
TRACE_COUNTER_INCREMENT ( S3DDC_GetHit ) ;
}
else
{
Record = FCacheRecordBuilder ( Request . Key ) . Build ( ) ;
}
2022-02-11 12:57:03 -05:00
}
2023-06-21 15:23:06 -04:00
RequestStats . AddLogicalRead ( Record . Get ( ) ) ;
RequestStats . Status = Status ;
StoreStats - > AddRequest ( RequestStats ) ;
2022-03-17 09:07:20 -04:00
OnComplete ( { Request . Name , MoveTemp ( Record ) . Get ( ) , Request . UserData , Status } ) ;
2022-02-11 12:57:03 -05:00
}
}
2022-02-14 14:43:39 -05:00
void FS3CacheStore : : PutValue (
const TConstArrayView < FCachePutValueRequest > Requests ,
IRequestOwner & Owner ,
FOnCachePutValueComplete & & OnComplete )
{
CompleteWithStatus ( Requests , OnComplete , EStatus : : Error ) ;
}
void FS3CacheStore : : GetValue (
2022-02-11 12:57:03 -05:00
const TConstArrayView < FCacheGetValueRequest > Requests ,
IRequestOwner & Owner ,
FOnCacheGetValueComplete & & OnComplete )
{
for ( const FCacheGetValueRequest & Request : Requests )
{
2022-03-17 09:07:20 -04:00
bool bOk ;
2022-02-11 12:57:03 -05:00
FValue Value ;
2023-06-21 15:23:06 -04:00
FRequestStats RequestStats ;
2023-06-23 10:51:52 -04:00
RequestStats . Name = Request . Name ;
2023-06-21 15:23:06 -04:00
RequestStats . Bucket = Request . Key . Bucket ;
RequestStats . Type = ERequestType : : Value ;
RequestStats . Op = ERequestOp : : Get ;
2022-02-11 12:57:03 -05:00
{
2022-03-17 09:07:20 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( S3DDC_GetValue ) ;
TRACE_COUNTER_INCREMENT ( S3DDC_Get ) ;
2023-06-21 15:23:06 -04:00
FRequestTimer RequestTimer ( RequestStats ) ;
bOk = GetCacheValue ( Request . Name , Request . Key , Request . Policy , Value , RequestStats ) ;
2022-03-17 09:07:20 -04:00
if ( bOk )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " %s: Cache hit for %s from '%s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Request . Key ) , * Request . Name ) ;
TRACE_COUNTER_INCREMENT ( S3DDC_GetHit ) ;
}
2021-04-28 16:22:18 -04:00
}
2023-06-21 15:23:06 -04:00
RequestStats . AddLogicalRead ( Value ) ;
RequestStats . Status = bOk ? EStatus : : Ok : EStatus : : Error ;
StoreStats - > AddRequest ( RequestStats ) ;
2022-03-17 09:07:20 -04:00
OnComplete ( { Request . Name , Request . Key , Value , Request . UserData , bOk ? EStatus : : Ok : EStatus : : Error } ) ;
2021-04-28 16:22:18 -04:00
}
}
2022-02-14 14:43:39 -05:00
void FS3CacheStore : : GetChunks (
2022-01-18 04:47:59 -05:00
const TConstArrayView < FCacheGetChunkRequest > Requests ,
2021-08-04 18:08:50 -04:00
IRequestOwner & Owner ,
2022-01-18 04:47:59 -05:00
FOnCacheGetChunkComplete & & OnComplete )
2021-04-28 16:22:18 -04:00
{
2022-02-11 12:57:03 -05:00
TArray < FCacheGetChunkRequest , TInlineAllocator < 16 > > SortedRequests ( Requests ) ;
SortedRequests . StableSort ( TChunkLess ( ) ) ;
bool bHasValue = false ;
FValue Value ;
FValueId ValueId ;
FCacheKey ValueKey ;
TUniquePtr < FArchive > ValueAr ;
FCompressedBufferReader ValueReader ;
FOptionalCacheRecord Record ;
for ( const FCacheGetChunkRequest & Request : SortedRequests )
2021-04-28 16:22:18 -04:00
{
2022-03-17 09:07:20 -04:00
EStatus Status = EStatus : : Error ;
FSharedBuffer Buffer ;
uint64 RawSize = 0 ;
2023-06-21 15:23:06 -04:00
FRequestStats RequestStats ;
2023-06-23 10:51:52 -04:00
RequestStats . Name = Request . Name ;
2023-06-21 15:23:06 -04:00
RequestStats . Bucket = Request . Key . Bucket ;
RequestStats . Type = Request . Id . IsNull ( ) ? ERequestType : : Value : ERequestType : : Record ;
RequestStats . Op = ERequestOp : : GetChunk ;
2022-02-11 12:57:03 -05:00
{
2022-03-17 09:07:20 -04:00
TRACE_CPUPROFILER_EVENT_SCOPE ( S3DDC_GetChunks ) ;
TRACE_COUNTER_INCREMENT ( S3DDC_Get ) ;
const bool bExistsOnly = EnumHasAnyFlags ( Request . Policy , ECachePolicy : : SkipData ) ;
2023-06-21 15:23:06 -04:00
FRequestTimer RequestTimer ( RequestStats ) ;
2022-03-17 09:07:20 -04:00
if ( ! ( bHasValue & & ValueKey = = Request . Key & & ValueId = = Request . Id ) | | ValueReader . HasSource ( ) < ! bExistsOnly )
2022-02-11 12:57:03 -05:00
{
2022-03-17 09:07:20 -04:00
ValueReader . ResetSource ( ) ;
ValueAr . Reset ( ) ;
ValueKey = { } ;
ValueId . Reset ( ) ;
Value . Reset ( ) ;
bHasValue = false ;
if ( Request . Id . IsValid ( ) )
2022-02-11 12:57:03 -05:00
{
2022-03-17 09:07:20 -04:00
if ( ! ( Record & & Record . Get ( ) . GetKey ( ) = = Request . Key ) )
2022-02-14 14:43:39 -05:00
{
2022-03-17 09:07:20 -04:00
FCacheRecordPolicyBuilder PolicyBuilder ( ECachePolicy : : None ) ;
PolicyBuilder . AddValuePolicy ( Request . Id , Request . Policy ) ;
Record . Reset ( ) ;
2023-06-21 15:23:06 -04:00
Record = GetCacheRecordOnly ( Request . Name , Request . Key , PolicyBuilder . Build ( ) , RequestStats ) ;
2022-03-17 09:07:20 -04:00
}
if ( Record )
{
if ( const FValueWithId & ValueWithId = Record . Get ( ) . GetValue ( Request . Id ) )
{
bHasValue = true ;
Value = ValueWithId ;
ValueId = Request . Id ;
ValueKey = Request . Key ;
2023-06-21 15:23:06 -04:00
GetCacheContent ( Request . Name , Request . Key , ValueId , Value , Request . Policy , ValueReader , ValueAr , RequestStats ) ;
2022-03-17 09:07:20 -04:00
}
}
}
else
{
ValueKey = Request . Key ;
2023-06-21 15:23:06 -04:00
bHasValue = GetCacheValueOnly ( Request . Name , Request . Key , Request . Policy , Value , RequestStats ) ;
2022-03-17 09:07:20 -04:00
if ( bHasValue )
{
2023-06-21 15:23:06 -04:00
GetCacheContent ( Request . Name , Request . Key , Request . Id , Value , Request . Policy , ValueReader , ValueAr , RequestStats ) ;
2022-02-14 14:43:39 -05:00
}
2022-02-11 12:57:03 -05:00
}
}
2022-03-17 09:07:20 -04:00
if ( bHasValue )
2022-02-11 12:57:03 -05:00
{
2022-03-17 09:07:20 -04:00
const uint64 RawOffset = FMath : : Min ( Value . GetRawSize ( ) , Request . RawOffset ) ;
RawSize = FMath : : Min ( Value . GetRawSize ( ) - RawOffset , Request . RawSize ) ;
TRACE_COUNTER_INCREMENT ( S3DDC_GetHit ) ;
if ( ! bExistsOnly )
2022-02-14 14:43:39 -05:00
{
2022-03-17 09:07:20 -04:00
Buffer = ValueReader . Decompress ( RawOffset , RawSize ) ;
2023-06-21 15:23:06 -04:00
RequestStats . LogicalReadSize + = Buffer . GetSize ( ) ;
2022-02-14 14:43:39 -05:00
}
2022-03-17 09:07:20 -04:00
Status = bExistsOnly | | Buffer . GetSize ( ) = = RawSize ? EStatus : : Ok : EStatus : : Error ;
2022-02-11 12:57:03 -05:00
}
}
2023-06-21 15:23:06 -04:00
RequestStats . Status = Status ;
StoreStats - > AddRequest ( RequestStats ) ;
UE_CLOG ( Status = = EStatus : : Ok , LogDerivedDataCache , Verbose , TEXT ( " %s: Cache hit for %s from '%s' " ) ,
* GetName ( ) , * WriteToString < 96 > ( Request . Key , ' / ' , Request . Id ) , * Request . Name ) ;
2022-03-17 09:07:20 -04:00
OnComplete ( { Request . Name , Request . Key , Request . Id , Request . RawOffset ,
RawSize , Value . GetRawHash ( ) , MoveTemp ( Buffer ) , Request . UserData , Status } ) ;
2021-04-28 16:22:18 -04:00
}
}
2022-02-14 14:43:39 -05:00
} // UE::DerivedData
2021-04-28 16:22:18 -04:00
2023-06-21 15:23:06 -04:00
# endif // WITH_S3_DDC_BACKEND
2022-01-11 11:57:38 -05:00
2022-02-14 14:43:39 -05:00
namespace UE : : DerivedData
2022-01-11 11:57:38 -05:00
{
2023-06-21 15:23:06 -04:00
ILegacyCacheStore * CreateS3CacheStore ( const TCHAR * Name , const TCHAR * Config , ICacheStoreOwner & Owner )
2022-01-11 11:57:38 -05:00
{
2023-06-21 15:23:06 -04:00
# if WITH_S3_DDC_BACKEND
FString ManifestPath ;
if ( ! FParse : : Value ( Config , TEXT ( " Manifest= " ) , ManifestPath ) )
{
UE_LOG ( LogDerivedDataCache , Error , TEXT ( " %s: Missing required parameter 'Manifest' " ) , Name ) ;
return nullptr ;
}
FString BaseUrl ;
if ( ! FParse : : Value ( Config , TEXT ( " BaseUrl= " ) , BaseUrl ) )
{
UE_LOG ( LogDerivedDataCache , Error , TEXT ( " %s: Missing required parameter 'BaseUrl' " ) , Name ) ;
return nullptr ;
}
FString Region ;
if ( ! FParse : : Value ( Config , TEXT ( " Region= " ) , Region ) )
{
UE_LOG ( LogDerivedDataCache , Error , TEXT ( " %s: Missing required parameter 'Region' " ) , Name ) ;
return nullptr ;
}
FString CanaryObjectKey ;
FParse : : Value ( Config , TEXT ( " Canary= " ) , CanaryObjectKey ) ;
FString CachePath = FPaths : : ProjectSavedDir ( ) / TEXT ( " S3DDC " ) ;
FString Key ;
if ( FParse : : Value ( Config , TEXT ( " EnvPathOverride= " ) , Key ) )
{
if ( FString Value = FPlatformMisc : : GetEnvironmentVariable ( * Key ) ; ! Value . IsEmpty ( ) )
{
CachePath = MoveTemp ( Value ) ;
UE_LOG ( LogDerivedDataCache , Log , TEXT ( " %s: Found environment variable %s=%s " ) , Name , * Key , * CachePath ) ;
}
if ( FString Value ; FPlatformMisc : : GetStoredValue ( TEXT ( " Epic Games " ) , TEXT ( " GlobalDataCachePath " ) , * Key , Value ) & & ! Value . IsEmpty ( ) )
{
CachePath = MoveTemp ( Value ) ;
UE_LOG ( LogDerivedDataCache , Log , TEXT ( " %s: Found registry key GlobalDataCachePath %s=%s " ) , Name , * Key , * CachePath ) ;
}
}
if ( CachePath = = TEXTVIEW ( " None " ) )
{
UE_LOG ( LogDerivedDataCache , Log , TEXT ( " %s: Disabled because the path is configured to 'None' " ) , Name ) ;
return nullptr ;
}
TUniquePtr < FS3CacheStore > Store ( new FS3CacheStore ( Name , * ManifestPath , * BaseUrl , * Region , * CanaryObjectKey , * CachePath , Owner ) ) ;
if ( ! Store - > IsUsable ( ) )
{
return nullptr ;
}
return Store . Release ( ) ;
# else
UE_LOG ( LogDerivedDataCache , Log , TEXT ( " S3 backend is not supported on the current platform. " ) ) ;
2022-01-11 11:57:38 -05:00
return nullptr ;
2023-06-21 15:23:06 -04:00
# endif
2022-01-11 11:57:38 -05:00
}
2022-02-14 14:43:39 -05:00
} // UE::DerivedData