You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
========================== MAJOR FEATURES + CHANGES ========================== Change 2946506 on 2016/04/18 by Steven.Hutton Update to Crash Reporter buggs table to add new search fields and inclusion of packages needed for e-mail reports. Change 3017807 on 2016/06/17 by Chris.Wood Improved Crash Report Process folder delete code as it could sometimes fail. [UE-30349] - Crash Report Process is leaving crashes in the landing zone that build up and block the queue Also added logging to Slack when stop request received instead of just when stop is complete. Change 3019367 on 2016/06/20 by Chris.Wood Improve Crash Report Process logging to track bad reads from S3. Also, better logging when CleanReport fails to delete folders. Change 3019376 on 2016/06/20 by Steve.Robb Clarification of assert message and comments which talk about 'null' TFunctions. Tidy-up of dead code. Change 3019409 on 2016/06/20 by Steve.Robb New Find and FindByPredicate algorithms for finding stuff in arbitrary containers. Change 3022658 on 2016/06/22 by Chris.Wood Discarding duplicated crash reports earlier in read from Data Router process to avoid clashes in the landing zone (CRP v1.1.11) [UE-30349] - Crash Report Process is leaving crashes in the landing zone that build up and block the queue Also improved logging to Slack with better layout, fixed event ordering and counting duplicates. Change 3022840 on 2016/06/22 by Steve.Robb Skipped UHT attributes removed. Change 3022907 on 2016/06/22 by Robert.Manuszewski Fixing crash when adding a new C++ class to project #jira UE-32333 Change 3023169 on 2016/06/22 by Steve.Robb Checks for UTHINGs in skipped preprocessor blocks. Fixes for skipped UTHINGs and some other parsing accidents. #jira UE-31627 Change 3023239 on 2016/06/22 by Steve.Robb Fix for JSON date parsing reported here: https://udn.unrealengine.com/questions/299342/fdatetime-json-serialization-bug.html Change 3026812 on 2016/06/24 by Mieszko.Zielinski Marked FEnvQueryInstance::AddItemData UEnvQueryItemType_Point specialization as AIMODULE_API #UE4 Change3028235on 2016/06/27 by Robert.Manuszewski PR #2535: BUGFIX: FPS pop-up updates when loading new stat file (Contributed by projectgheist) Change 3028282 on 2016/06/27 by Steve.Robb Fix for missing UFUNCTION check in skipped preprocessor blocks. #jira UE-31627 Change 3028284 on 2016/06/27 by Steve.Robb Debuggability improvements and coding standards changes. Change 3028343 on 2016/06/27 by Steve.Robb Fix for UHT error in WEX. #jira UE-32464 Change 3028393 on 2016/06/27 by Steve.Robb Fix for hot reload of enums finding the old enum. Fix to stop SPropertyEditorNumeric caching the enum flags. #jira UE-31658 Change 3030362 on 2016/06/28 by Robert.Manuszewski Fixing hang when cooking. Change 3030462 on 2016/06/28 by Steve.Robb Assert added to PackageTools::GetFilteredPackageList() to help with catching a bug reported in the wild. #jira UE-32001 Change 3034341 on 2016/06/30 by Robert.Manuszewski Modified crash handling code (on Windows) to handle two threads crashing at the same time properly. Previously the second crash would force the process to exit before generating the crash report. Added 'debug twothreadsgpf' command to test the functionality. Change 3034342 on 2016/06/30 by John.Mahoney Fix for crash when loading an empty cached asset registry. #jira UE-32232 Change 3035599 on 2016/07/01 by Chris.Wood Added support for CrashType string to Crash Report Process. CRP v1.1.12 [UE-30592] - Crash Reporter should determine crash type on client and pass string to server Also fixes problem with reports falling back on the legacy WER metadata when a crash context exists. They now only read the error message from metadata if available and keep crash context data when possible. Added in missing crash context parameters that have been added to clients but not known by the server. Change 3035787 on 2016/07/01 by John.Mahoney Fix for crash when DuplicateRedirects does not contain the DependentObject when saving dependencies. It will still fall through to the assertion below, but it will now fail with a useful error message instead of a generic 'Pair != nullptr' from Map.h. #jira UE-30189 Change 3036933 on 2016/07/04 by Steve.Robb Proper forwarding constructor for FAsyncTask. Change 3036938 on 2016/07/04 by Steve.Robb Fix for CDO hot reload corrupting memory when replacing references inside structs. #jira UE-29335 Change 3036960 on 2016/07/04 by Steve.Robb Fix for FAnsiAllocator::ResizeAllocation when resizing to zero. Change 3037423 on 2016/07/05 by Steve.Robb FModuleManager::UnloadOrAbandonModuleWithCallback split into two instead of switching behavior with a bool. Change 3037464 on 2016/07/05 by Steve.Robb HotReload.cpp cleanup: Deep nesting flattened. Linear array searches replaced with maps. FHotReloadModule::GetGameModules made into a non-member function and split into two. Comment and coding standard fixes. Change 3037741 on 2016/07/05 by John.Mahoney Fix for COTF not checking the correct timestamps on startup. #jira UE-31023 Change3037846on 2016/07/05 by Steve.Robb Fix for compile button disappearing on a bad compile. #jira UE-31575 Change 3037994 on 2016/07/05 by Steve.Robb Static analysis fixes: warning C6308: 'realloc' might return null pointer: assigning null pointer to 'Data', which is passed as an argument to 'realloc', will cause the original memory block to be leaked. Change 3039186 on 2016/07/06 by Robert.Manuszewski Enabling crash callstack logging by default. Change 3039220 on 2016/07/06 by Steve.Robb Static analysis fixes: warning C28159: Consider using 'InitiateSystemShutdownEx' instead of 'ExitWindowsEx'. Reason: Legacy API. Rearchitect to avoid Reboot warning C6001: Using uninitialized memory 'UserNameLength' warning C6001: Using uninitialized memory 'DomainNameLength' Change 3039230 on 2016/07/06 by Steve.Robb Fix for VC internal compiler errors. Change 3039237 on 2016/07/06 by Steve.Robb Static analysis fix: warning C6385: Reading invalid data from 'Path': the readable size is '400' bytes, but 'PathCurrentDepth' bytes may be read. Change 3039287 on 2016/07/06 by Steve.Robb Static analysis fixes: warning C6509: Invalid annotation: 'return' cannot be referenced in some contexts warning C6101: Returning uninitialized memory '*lpdwExitCode'. A successful path through the function does not set the named _Out_ parameter. warning C6387: '_Param_(1)' could be '0': this does not adhere to the specification for the function 'IMoniker::BindToStorage'. warning C6387: '_Param_(1)' could be '0': this does not adhere to the specification for the function 'IMoniker::BindToObject'. warning C6031: Return value ignored: 'CoCreateInstance'. Change 3039359 on 2016/07/06 by Graeme.Thornton Compile error fix for FAsyncTask, courtesy of SteveR Change 3039534 on 2016/07/06 by Steve.Robb Static analysis fix: warning C6319: Use of the comma-operator in a tested expression causes the left argument to be ignored when it has no side-effects. Change 3039545 on 2016/07/06 by Steve.Robb Static analysis fix: warning C6297: Arithmetic overflow: 32-bit value is shifted, then cast to 64-bit value. Results might not be an expected value. Change 3039578 on 2016/07/06 by Steve.Robb Static analysis fix: warning C6263: Using _alloca in a loop: this can quickly overflow stack. Change 3039623 on 2016/07/06 by Steve.Robb Static analysis fixes: warning C6011: Dereferencing NULL pointer 'X' warning C6308:'realloc' might return null pointer: assigning null pointer to 'X', which is passed as an argument to 'realloc', will cause the original memory block to be leaked. warning C6385: Reading invalid data from 'X': the readable size is 'Y' bytes, but 'Z' bytes may be read. warning C6386: Buffer overrun while writing to 'X': the writable size is 'Y' bytes, but 'Z' bytes might be written. warning C28182: Dereferencing NULL pointer. 'X' contains the same NULL value as 'Y' did. Change 3039630 on 2016/07/06 by John.Mahoney Fix for crash when spawning an actor using a template object that has instance components. UActorComponent::PostInitProperties was adding itself to the owner's InstanceComponents array, resulting in a realloc of that array and invalidating the reference that the owner's ObjectInitializer was trying to replace while instantiating that property. The new instance component will be added to the array as part of the owner's initialization anyway, so it is not necessary to do it here. #jira UE-29123 Change 3039664 on 2016/07/06 by Steve.Robb Static analysis fixes: warning C6386: Buffer overrun while writing to 'NewKeys': the writable size is 'NewIndexSize*4' bytes, but '8' bytes might be written. warning C6386: Buffer overrun while writing to 'NewHeapIndexes': the writable size is 'NewIndexSize*4' bytes, but '8' bytes might be written. Change 3039673 on 2016/07/06 by Steve.Robb Static analysis fix: warning C6011: Dereferencing NULL pointer 'v'. Change 3039690 on 2016/07/06 by Steve.Robb Static analysis fixes: warning C6011: Dereferencing NULL pointer 'X'. warning C6246: Local declaration of 'X' hides declaration of the same name in outer scope. warning C6262: Function uses '121180' bytes of stack: exceeds /analyze:stacksize '81940'. Consider moving some data to heap. warning C6263: Using _alloca in a loop: this can quickly overflow stack. Change 3040868 on 2016/07/07 by Graeme.Thornton Config based class stripping for server builds Change 3040872 on 2016/07/07 by Graeme.Thornton Remove "return false" NeedsLoadForServer functions from engine code Change3040997on 2016/07/07 by Steve.Robb Static analysis fixes: warning C6011: Dereferencing NULL pointer 'Landscape'. warning C6011: Dereferencing NULL pointer 'rhs.Allocation.LayerInfo'. warning C6011: Dereferencing NULL pointer 'lhs.Allocation.LayerInfo'. Change 3041004 on 2016/07/07 by Steve.Robb Static analysis fix: warning C6336: Arithmetic operator has precedence over question operator, use parentheses to clarify intent. Change 3041014 on 2016/07/07 by Steve.Robb Static analysis fix: warning C6287: Redundant code: the left and right sub-expressions are identical. Change 3041111 on 2016/07/07 by Steve.Robb Removal of an obsolete error message about INI file case sensitivity. Change 3041150 on 2016/07/07 by Steve.Robb Static analysis fix: warning C6289: Incorrect operator: mutual exclusion over || is always a non-zero constant. Did you intend to use && instead? Change 3041274 on 2016/07/07 by Steve.Robb Static analysis fixes: warning C6001: Using uninitialized memory 'X'. Change 3041294 on 2016/07/07 by Chris.Wood Fixed protocol buffer and decompression errors in Crash Report Process (v.1.1.14) [UE-32151] - High number of crashes read from S3 by Crash Report Process are failing to unpack Size of buffer received from S3 is incorrect for some records. Fixed read problems by using size header value instead of stream length. Increased buffer size for decompression as this was sometimes too small. Modified S3 reading code to look for multiple records in each downloaded file. Change 3041472 on 2016/07/07 by Steve.Robb Static analysis fixes: warning C6294: Ill-defined for-loop: initial condition does not satisfy test. Loop body not executed. warning C6201: Index '1' is out of valid index range '0' to '0' for possibly stack allocated buffer 'NewHistory.Nodes'. Change 3043074 on 2016/07/08 by John.Mahoney Fix for COTF incorrectly reconstructing the original asset path based on the sandbox path when the game name differs from the game folder name. Fix for COTF GetFiles not handling absolute GameDir paths properly. #jira UE-31023 Change 3044461 on 2016/07/11 by Steve.Robb Static analysis fix: warning C6386: Buffer overrun while writing to 'Attributes': the writable size is '16384' bytes, but '-8' bytes might be written. Change 3044470 on 2016/07/11 by Steve.Robb Static analysis fix: warning C6011: Dereferencing NULL pointer 'Node.Sequence'. Change 3044476 on 2016/07/11 by Steve.Robb Static analysis fix: warning C6011: Dereferencing NULL pointer 'Property'. Change 3044551 on 2016/07/11 by Steve.Robb Static analysis fix: warning C28182: Dereferencing NULL pointer. 'Node' contains the same NULL value as 'KeyAreaNode' did. Change 3044664 on 2016/07/11 by Steve.Robb Static analysis fixes: warning C6011: Dereferencing NULL pointer 'ToLandscape->SplineComponent'. warning C28182: Dereferencing NULL pointer. 'SplinesComponent' contains the same NULL value as 'Landscape->SplineComponent' did. warning C6011: Dereferencing NULL pointer 'Landscape->SplineComponent'. warning C6385: Reading invalid data from 'out': the readable size is 'sizeof(kiss_fft_cpx)*Dims[0]*Dims[1]' bytes, but '16' bytes may be read. Change 3044716 on 2016/07/11 by Steve.Robb Static analysis fix: warning C6385: Reading invalid data from 'this->ScreenSize': the readable size is '32' bytes, but '-4' bytes may be read. Change 3044717 on 2016/07/11 by Steve.Robb Static analysis fix: warning C28182: Dereferencing NULL pointer. 'Window' contains the same NULL value as 'ElementType * Window=AllWindows.FindByPredicate((*FStaticMeshEditorTest::RunTest::<lambda_46fd0093f3912289e870263afe1fcb2e>(ExpectedTitle)))' did. This appears to be a false positive. Change 3044787 on 2016/07/11 by Steve.Robb Static analysis fixes: warning C6011: Dereferencing NULL pointer 'FbxObject'. warning C28182: Dereferencing NULL pointer. 'Node' contains the same NULL value as 'RigidMeshNode' did. warning C28182: Dereferencing NULL pointer. 'Node' contains the same NULL value as 'Result' did. Change 3045933 on 2016/07/12 by Steve.Robb Overloading support for TSharedPtr, TSharedRef and TWeakPtr. Change 3045960 on 2016/07/12 by Robert.Manuszewski Fixing a crash in Portal (and any other program that uses UObjects and GCs, with the exception of UHT) caused by classes not having their token stream assembled. Change 3045963 on 2016/07/12 by Steve.Robb PLATFORM_COMPILER_HAS_EXPLICIT_OPERATORS, FORCEINLINE_EXPLICIT_OPERATOR_BOOL and SAFE_BOOL_OPERATORS macros removed. THasOperatorEquals and THasOperatorNotEquals traits moved to their own header. Change3045967on 2016/07/12 by Steve.Robb Initializer list support for TArray and TSet. Change 3045968 on 2016/07/12 by Robert.Manuszewski Fixing an ensure after typing 'stat dumphitches' in console. Change 3045992 on 2016/07/12 by Robert.Manuszewski Making sure CoreUObject headers are included for programs that don't include the engine (fixing MinidumpDiagnostics CIS failure) Change 3047870 on 2016/07/13 by Steven.Hutton Updated CRW to entity framework with repository models. #rb none Change 3047871 on 2016/07/13 by Steven.Hutton Add repository models #rb none Change 3049468 on 2016/07/14 by Steven.Hutton Fix broken project files. #rb none #lockdown Nick.Penwarden [CL 3050320 by Robert Manuszewski in Main branch]
1037 lines
31 KiB
C++
1037 lines
31 KiB
C++
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "NetworkFileSystemPrivatePCH.h"
|
|
#include "PackageName.h"
|
|
#include "TargetPlatform.h"
|
|
|
|
|
|
/* FNetworkFileServerClientConnection structors
|
|
*****************************************************************************/
|
|
|
|
FNetworkFileServerClientConnection::FNetworkFileServerClientConnection( const FFileRequestDelegate& InFileRequestDelegate,
|
|
const FRecompileShadersDelegate& InRecompileShadersDelegate, const TArray<ITargetPlatform*>& InActiveTargetPlatforms )
|
|
: LastHandleId(0)
|
|
, Sandbox(NULL)
|
|
, ActiveTargetPlatforms(InActiveTargetPlatforms)
|
|
{
|
|
if (InFileRequestDelegate.IsBound())
|
|
{
|
|
FileRequestDelegate = InFileRequestDelegate;
|
|
}
|
|
|
|
if (InRecompileShadersDelegate.IsBound())
|
|
{
|
|
RecompileShadersDelegate = InRecompileShadersDelegate;
|
|
}
|
|
}
|
|
|
|
|
|
FNetworkFileServerClientConnection::~FNetworkFileServerClientConnection( )
|
|
{
|
|
// close all the files the client had opened through us when the client disconnects
|
|
for (TMap<uint64, IFileHandle*>::TIterator It(OpenFiles); It; ++It)
|
|
{
|
|
delete It.Value();
|
|
}
|
|
|
|
delete Sandbox;
|
|
Sandbox = NULL;
|
|
}
|
|
|
|
/* FStreamingNetworkFileServerConnection implementation
|
|
*****************************************************************************/
|
|
void FNetworkFileServerClientConnection::ConvertClientFilenameToServerFilename(FString& FilenameToConvert)
|
|
{
|
|
if (FilenameToConvert.StartsWith(ConnectedEngineDir))
|
|
{
|
|
FilenameToConvert = FilenameToConvert.Replace(*ConnectedEngineDir, *(FPaths::EngineDir()));
|
|
}
|
|
else if (FilenameToConvert.StartsWith(ConnectedGameDir))
|
|
{
|
|
if ( FPaths::IsProjectFilePathSet() )
|
|
{
|
|
FilenameToConvert = FilenameToConvert.Replace(*ConnectedGameDir, *(FPaths::GetPath(FPaths::GetProjectFilePath()) + TEXT("/")));
|
|
}
|
|
else
|
|
{
|
|
#if !IS_PROGRAM
|
|
// UnrealFileServer has a GameDir of ../../../Engine/Programs/UnrealFileServer.
|
|
// We do *not* want to replace the directory in that case.
|
|
FilenameToConvert = FilenameToConvert.Replace(*ConnectedGameDir, *(FPaths::GameDir()));
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fixup sandbox paths to match what package loading will request on the client side. e.g.
|
|
* Sandbox path: "../../../Elemental/Content/Elemental/Effects/FX_Snow_Cracks/Crack_02/Materials/M_SnowBlast.uasset ->
|
|
* client path: "../../../Samples/Showcases/Elemental/Content/Elemental/Effects/FX_Snow_Cracks/Crack_02/Materials/M_SnowBlast.uasset"
|
|
* This ensures that devicelocal-cached files will be properly timestamp checked before deletion.
|
|
*/
|
|
static TMap<FString, FDateTime> FixupSandboxPathsForClient(FSandboxPlatformFile* Sandbox, const TMap<FString, FDateTime>& SandboxPaths, const FString& LocalEngineDir, const FString& LocalGameDir, bool bLowerCaseFiles)
|
|
{
|
|
TMap<FString, FDateTime> FixedFiletimes;
|
|
FString SandboxEngine = Sandbox->ConvertToSandboxPath(*LocalEngineDir);
|
|
if (SandboxEngine.EndsWith(TEXT("/"), ESearchCase::CaseSensitive) == false)
|
|
{
|
|
SandboxEngine += TEXT("/");
|
|
}
|
|
|
|
// we need to add an extra bit to the game path to make the sandbox convert it correctly (investigate?)
|
|
// @todo: double check this
|
|
FString SandboxGame = Sandbox->ConvertToSandboxPath(*(LocalGameDir + TEXT("a.txt"))).Replace(TEXT("a.txt"), TEXT(""));
|
|
|
|
// since the sandbox remaps from A/B/C to C, and the client has no idea of this, we need to put the files
|
|
// into terms of the actual LocalGameDir, which is all that the client knows about
|
|
for (TMap<FString, FDateTime>::TConstIterator It(SandboxPaths); It; ++It)
|
|
{
|
|
FString Fixed = Sandbox->ConvertToSandboxPath(*It.Key());
|
|
Fixed = Fixed.Replace(*SandboxEngine, *LocalEngineDir);
|
|
Fixed = Fixed.Replace(*SandboxGame, *LocalGameDir);
|
|
|
|
if (bLowerCaseFiles)
|
|
{
|
|
Fixed = Fixed.ToLower();
|
|
}
|
|
FixedFiletimes.Add(Fixed, It.Value());
|
|
}
|
|
return FixedFiletimes;
|
|
}
|
|
|
|
void FNetworkFileServerClientConnection::ConvertServerFilenameToClientFilename(FString& FilenameToConvert)
|
|
{
|
|
if (FilenameToConvert.StartsWith(FPaths::EngineDir()))
|
|
{
|
|
FilenameToConvert = FilenameToConvert.Replace(*(FPaths::EngineDir()), *ConnectedEngineDir);
|
|
}
|
|
else if (FPaths::IsProjectFilePathSet())
|
|
{
|
|
if (FilenameToConvert.StartsWith(FPaths::GetPath(FPaths::GetProjectFilePath())))
|
|
{
|
|
FilenameToConvert = FilenameToConvert.Replace(*(FPaths::GetPath(FPaths::GetProjectFilePath()) + TEXT("/")), *ConnectedGameDir);
|
|
}
|
|
}
|
|
#if !IS_PROGRAM
|
|
else if (FilenameToConvert.StartsWith(FPaths::GameDir()))
|
|
{
|
|
// UnrealFileServer has a GameDir of ../../../Engine/Programs/UnrealFileServer.
|
|
// We do *not* want to replace the directory in that case.
|
|
FilenameToConvert = FilenameToConvert.Replace(*(FPaths::GameDir()), *ConnectedGameDir);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static FCriticalSection SocketCriticalSection;
|
|
|
|
bool FNetworkFileServerClientConnection::ProcessPayload(FArchive& Ar)
|
|
{
|
|
FBufferArchive Out;
|
|
bool Result = true;
|
|
|
|
// first part of the payload is always the command
|
|
uint32 Cmd;
|
|
Ar << Cmd;
|
|
|
|
UE_LOG(LogFileServer, Verbose, TEXT("Processing payload with Cmd %d"), Cmd);
|
|
|
|
// what type of message is this?
|
|
NFS_Messages::Type Msg = NFS_Messages::Type(Cmd);
|
|
|
|
// make sure the first thing is GetFileList which initializes the game/platform
|
|
checkf(Msg == NFS_Messages::GetFileList || Msg == NFS_Messages::Heartbeat || Sandbox != NULL, TEXT("The first client message MUST be GetFileList, not %d"), (int32)Msg);
|
|
|
|
// process the message!
|
|
bool bSendUnsolicitedFiles = false;
|
|
|
|
{
|
|
FScopeLock SocketLock(&SocketCriticalSection);
|
|
|
|
switch (Msg)
|
|
{
|
|
case NFS_Messages::OpenRead:
|
|
ProcessOpenFile(Ar, Out, false);
|
|
break;
|
|
|
|
case NFS_Messages::OpenWrite:
|
|
ProcessOpenFile(Ar, Out, true);
|
|
break;
|
|
|
|
case NFS_Messages::Read:
|
|
ProcessReadFile(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::Write:
|
|
ProcessWriteFile(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::Seek:
|
|
ProcessSeekFile(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::Close:
|
|
ProcessCloseFile(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::MoveFile:
|
|
ProcessMoveFile(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::DeleteFile:
|
|
ProcessDeleteFile(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::GetFileInfo:
|
|
ProcessGetFileInfo(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::CopyFile:
|
|
ProcessCopyFile(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::SetTimeStamp:
|
|
ProcessSetTimeStamp(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::SetReadOnly:
|
|
ProcessSetReadOnly(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::CreateDirectory:
|
|
ProcessCreateDirectory(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::DeleteDirectory:
|
|
ProcessDeleteDirectory(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::DeleteDirectoryRecursively:
|
|
ProcessDeleteDirectoryRecursively(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::ToAbsolutePathForRead:
|
|
ProcessToAbsolutePathForRead(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::ToAbsolutePathForWrite:
|
|
ProcessToAbsolutePathForWrite(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::ReportLocalFiles:
|
|
ProcessReportLocalFiles(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::GetFileList:
|
|
Result = ProcessGetFileList(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::Heartbeat:
|
|
ProcessHeartbeat(Ar, Out);
|
|
break;
|
|
|
|
case NFS_Messages::SyncFile:
|
|
ProcessSyncFile(Ar, Out);
|
|
bSendUnsolicitedFiles = true;
|
|
break;
|
|
|
|
case NFS_Messages::RecompileShaders:
|
|
ProcessRecompileShaders(Ar, Out);
|
|
break;
|
|
|
|
default:
|
|
|
|
UE_LOG(LogFileServer, Error, TEXT("Bad incomming message tag (%d)."), (int32)Msg);
|
|
}
|
|
}
|
|
|
|
|
|
// send back a reply if the command wrote anything back out
|
|
if (Out.Num() && Result )
|
|
{
|
|
int32 NumUnsolictedFiles = 0;
|
|
|
|
|
|
if (bSendUnsolicitedFiles)
|
|
{
|
|
int64 MaxMemoryAllowed = 50 * 1024 * 1024;
|
|
for (const auto& Filename : UnsolictedFiles)
|
|
{
|
|
// get file timestamp and send it to client
|
|
FDateTime ServerTimeStamp = Sandbox->GetTimeStamp(*Filename);
|
|
|
|
TArray<uint8> Contents;
|
|
// open file
|
|
int64 FileSize = Sandbox->FileSize(*Filename);
|
|
|
|
if (MaxMemoryAllowed > FileSize)
|
|
{
|
|
MaxMemoryAllowed -= FileSize;
|
|
++NumUnsolictedFiles;
|
|
}
|
|
}
|
|
Out << NumUnsolictedFiles;
|
|
}
|
|
|
|
UE_LOG(LogFileServer, Verbose, TEXT("Returning payload with %d bytes"), Out.Num());
|
|
|
|
// send back a reply
|
|
Result &= SendPayload( Out );
|
|
|
|
TArray<FString> UnprocessedUnsolictedFiles;
|
|
UnprocessedUnsolictedFiles.Empty(NumUnsolictedFiles);
|
|
|
|
if (bSendUnsolicitedFiles && Result )
|
|
{
|
|
for (int32 Index = 0; Index < NumUnsolictedFiles; Index++)
|
|
{
|
|
FBufferArchive OutUnsolicitedFile;
|
|
PackageFile(UnsolictedFiles[Index], OutUnsolicitedFile);
|
|
|
|
UE_LOG(LogFileServer, Display, TEXT("Returning unsolicited file %s with %d bytes"), *UnsolictedFiles[Index], OutUnsolicitedFile.Num());
|
|
|
|
Result &= SendPayload(OutUnsolicitedFile);
|
|
}
|
|
UnsolictedFiles.RemoveAt(0, NumUnsolictedFiles);
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogFileServer, Verbose, TEXT("Done Processing payload with Cmd %d Total Size sending %d "), Cmd,Out.TotalSize());
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessOpenFile( FArchive& In, FArchive& Out, bool bIsWriting )
|
|
{
|
|
// Get filename
|
|
FString Filename;
|
|
In << Filename;
|
|
|
|
bool bAppend = false;
|
|
bool bAllowRead = false;
|
|
|
|
if (bIsWriting)
|
|
{
|
|
In << bAppend;
|
|
In << bAllowRead;
|
|
}
|
|
|
|
// todo: clients from the same ip address "could" be trying to write to the same file in the same sandbox (for example multiple windows clients)
|
|
// should probably have the sandbox write to separate files for each client
|
|
// not important for now
|
|
|
|
ConvertClientFilenameToServerFilename(Filename);
|
|
|
|
if (bIsWriting)
|
|
{
|
|
// Make sure the directory exists...
|
|
Sandbox->CreateDirectoryTree(*(FPaths::GetPath(Filename)));
|
|
}
|
|
|
|
TArray<FString> NewUnsolictedFiles;
|
|
FileRequestDelegate.ExecuteIfBound(Filename, ConnectedPlatformName, NewUnsolictedFiles);
|
|
|
|
FDateTime ServerTimeStamp = Sandbox->GetTimeStamp(*Filename);
|
|
int64 ServerFileSize = 0;
|
|
IFileHandle* File = bIsWriting ? Sandbox->OpenWrite(*Filename, bAppend, bAllowRead) : Sandbox->OpenRead(*Filename);
|
|
if (!File)
|
|
{
|
|
UE_LOG(LogFileServer, Display, TEXT("Open request for %s failed for file %s."), bIsWriting ? TEXT("Writing") : TEXT("Reading"), *Filename);
|
|
ServerTimeStamp = FDateTime::MinValue(); // if this was a directory, this will make sure it is not confused with a zero byte file
|
|
}
|
|
else
|
|
{
|
|
ServerFileSize = File->Size();
|
|
}
|
|
|
|
uint64 HandleId = ++LastHandleId;
|
|
OpenFiles.Add( HandleId, File );
|
|
|
|
Out << HandleId;
|
|
Out << ServerTimeStamp;
|
|
Out << ServerFileSize;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessReadFile( FArchive& In, FArchive& Out )
|
|
{
|
|
// Get Handle ID
|
|
uint64 HandleId = 0;
|
|
In << HandleId;
|
|
|
|
int64 BytesToRead = 0;
|
|
In << BytesToRead;
|
|
|
|
int64 BytesRead = 0;
|
|
IFileHandle* File = FindOpenFile(HandleId);
|
|
|
|
if (File)
|
|
{
|
|
uint8* Dest = (uint8*)FMemory::Malloc(BytesToRead);
|
|
|
|
if (File->Read(Dest, BytesToRead))
|
|
{
|
|
BytesRead = BytesToRead;
|
|
Out << BytesRead;
|
|
Out.Serialize(Dest, BytesRead);
|
|
}
|
|
else
|
|
{
|
|
Out << BytesRead;
|
|
}
|
|
|
|
FMemory::Free(Dest);
|
|
}
|
|
else
|
|
{
|
|
Out << BytesRead;
|
|
}
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessWriteFile( FArchive& In, FArchive& Out )
|
|
{
|
|
// Get Handle ID
|
|
uint64 HandleId = 0;
|
|
In << HandleId;
|
|
|
|
int64 BytesWritten = 0;
|
|
IFileHandle* File = FindOpenFile(HandleId);
|
|
|
|
if (File)
|
|
{
|
|
int64 BytesToWrite = 0;
|
|
In << BytesToWrite;
|
|
|
|
uint8* Source = (uint8*)FMemory::Malloc(BytesToWrite);
|
|
In.Serialize(Source, BytesToWrite);
|
|
|
|
if (File->Write(Source, BytesToWrite))
|
|
{
|
|
BytesWritten = BytesToWrite;
|
|
}
|
|
|
|
FMemory::Free(Source);
|
|
}
|
|
|
|
Out << BytesWritten;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessSeekFile( FArchive& In, FArchive& Out )
|
|
{
|
|
// Get Handle ID
|
|
uint64 HandleId = 0;
|
|
In << HandleId;
|
|
|
|
int64 NewPosition;
|
|
In << NewPosition;
|
|
|
|
int64 SetPosition = -1;
|
|
IFileHandle* File = FindOpenFile(HandleId);
|
|
|
|
if (File && File->Seek(NewPosition))
|
|
{
|
|
SetPosition = File->Tell();
|
|
}
|
|
|
|
Out << SetPosition;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessCloseFile( FArchive& In, FArchive& Out )
|
|
{
|
|
// Get Handle ID
|
|
uint64 HandleId = 0;
|
|
In << HandleId;
|
|
|
|
uint32 Closed = 0;
|
|
IFileHandle* File = FindOpenFile(HandleId);
|
|
|
|
if (File)
|
|
{
|
|
Closed = 1;
|
|
OpenFiles.Remove(HandleId);
|
|
|
|
delete File;
|
|
}
|
|
|
|
Out << Closed;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessGetFileInfo( FArchive& In, FArchive& Out )
|
|
{
|
|
// Get filename
|
|
FString Filename;
|
|
In << Filename;
|
|
|
|
ConvertClientFilenameToServerFilename(Filename);
|
|
|
|
FFileInfo Info;
|
|
Info.FileExists = Sandbox->FileExists(*Filename);
|
|
|
|
// if the file exists, cook it if necessary (the FileExists flag won't change value based on this callback)
|
|
// without this, the server can return the uncooked file size, which can cause reads off the end
|
|
if (Info.FileExists)
|
|
{
|
|
TArray<FString> NewUnsolictedFiles;
|
|
FileRequestDelegate.ExecuteIfBound(Filename, ConnectedPlatformName, NewUnsolictedFiles);
|
|
}
|
|
|
|
// get the rest of the info
|
|
Info.ReadOnly = Sandbox->IsReadOnly(*Filename);
|
|
Info.Size = Sandbox->FileSize(*Filename);
|
|
Info.TimeStamp = Sandbox->GetTimeStamp(*Filename);
|
|
Info.AccessTimeStamp = Sandbox->GetAccessTimeStamp(*Filename);
|
|
|
|
Out << Info.FileExists;
|
|
Out << Info.ReadOnly;
|
|
Out << Info.Size;
|
|
Out << Info.TimeStamp;
|
|
Out << Info.AccessTimeStamp;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessMoveFile( FArchive& In, FArchive& Out )
|
|
{
|
|
FString From;
|
|
In << From;
|
|
FString To;
|
|
In << To;
|
|
|
|
ConvertClientFilenameToServerFilename(From);
|
|
ConvertClientFilenameToServerFilename(To);
|
|
|
|
uint32 Success = Sandbox->MoveFile(*To, *From);
|
|
Out << Success;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessDeleteFile( FArchive& In, FArchive& Out )
|
|
{
|
|
FString Filename;
|
|
In << Filename;
|
|
|
|
ConvertClientFilenameToServerFilename(Filename);
|
|
|
|
uint32 Success = Sandbox->DeleteFile(*Filename);
|
|
Out << Success;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessReportLocalFiles( FArchive& In, FArchive& Out )
|
|
{
|
|
// get the list of files on the other end
|
|
TMap<FString, FDateTime> ClientFileTimes;
|
|
In << ClientFileTimes;
|
|
|
|
// go over them and compare times to this side
|
|
TArray<FString> OutOfDateFiles;
|
|
|
|
for (TMap<FString, FDateTime>::TIterator It(ClientFileTimes); It; ++It)
|
|
{
|
|
FString ClientFile = It.Key();
|
|
ConvertClientFilenameToServerFilename(ClientFile);
|
|
// get the local timestamp
|
|
FDateTime Timestamp = Sandbox->GetTimeStamp(*ClientFile);
|
|
|
|
// if it's newer than the client/remote timestamp, it's newer here, so tell the other side it's out of date
|
|
if (Timestamp > It.Value())
|
|
{
|
|
OutOfDateFiles.Add(ClientFile);
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogFileServer, Display, TEXT("There were %d out of date files"), OutOfDateFiles.Num());
|
|
}
|
|
|
|
|
|
/** Copies file. */
|
|
void FNetworkFileServerClientConnection::ProcessCopyFile( FArchive& In, FArchive& Out )
|
|
{
|
|
FString To;
|
|
FString From;
|
|
In << To;
|
|
In << From;
|
|
|
|
ConvertClientFilenameToServerFilename(To);
|
|
ConvertClientFilenameToServerFilename(From);
|
|
|
|
bool Success = Sandbox->CopyFile(*To, *From);
|
|
Out << Success;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessSetTimeStamp( FArchive& In, FArchive& Out )
|
|
{
|
|
FString Filename;
|
|
FDateTime Timestamp;
|
|
In << Filename;
|
|
In << Timestamp;
|
|
|
|
ConvertClientFilenameToServerFilename(Filename);
|
|
|
|
Sandbox->SetTimeStamp(*Filename, Timestamp);
|
|
|
|
// Need to sends something back otherwise the response won't get sent at all.
|
|
bool Success = true;
|
|
Out << Success;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessSetReadOnly( FArchive& In, FArchive& Out )
|
|
{
|
|
FString Filename;
|
|
bool bReadOnly;
|
|
In << Filename;
|
|
In << bReadOnly;
|
|
|
|
ConvertClientFilenameToServerFilename(Filename);
|
|
|
|
bool Success = Sandbox->SetReadOnly(*Filename, bReadOnly);
|
|
Out << Success;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessCreateDirectory( FArchive& In, FArchive& Out )
|
|
{
|
|
FString Directory;
|
|
In << Directory;
|
|
|
|
ConvertClientFilenameToServerFilename(Directory);
|
|
|
|
bool bSuccess = Sandbox->CreateDirectory(*Directory);
|
|
Out << bSuccess;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessDeleteDirectory( FArchive& In, FArchive& Out )
|
|
{
|
|
FString Directory;
|
|
In << Directory;
|
|
|
|
ConvertClientFilenameToServerFilename(Directory);
|
|
|
|
bool bSuccess = Sandbox->DeleteDirectory(*Directory);
|
|
Out << bSuccess;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessDeleteDirectoryRecursively( FArchive& In, FArchive& Out )
|
|
{
|
|
FString Directory;
|
|
In << Directory;
|
|
|
|
ConvertClientFilenameToServerFilename(Directory);
|
|
|
|
bool bSuccess = Sandbox->DeleteDirectoryRecursively(*Directory);
|
|
Out << bSuccess;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessToAbsolutePathForRead( FArchive& In, FArchive& Out )
|
|
{
|
|
FString Filename;
|
|
In << Filename;
|
|
|
|
ConvertClientFilenameToServerFilename(Filename);
|
|
|
|
Filename = Sandbox->ConvertToAbsolutePathForExternalAppForRead(*Filename);
|
|
Out << Filename;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessToAbsolutePathForWrite( FArchive& In, FArchive& Out )
|
|
{
|
|
FString Filename;
|
|
In << Filename;
|
|
|
|
ConvertClientFilenameToServerFilename(Filename);
|
|
|
|
Filename = Sandbox->ConvertToAbsolutePathForExternalAppForWrite(*Filename);
|
|
Out << Filename;
|
|
}
|
|
|
|
|
|
bool FNetworkFileServerClientConnection::ProcessGetFileList( FArchive& In, FArchive& Out )
|
|
{
|
|
// get the list of directories to process
|
|
TArray<FString> TargetPlatformNames;
|
|
FString GameName;
|
|
FString EngineRelativePath;
|
|
FString GameRelativePath;
|
|
TArray<FString> RootDirectories;
|
|
bool bIsStreamingRequest = false;
|
|
|
|
In << TargetPlatformNames;
|
|
In << GameName;
|
|
In << EngineRelativePath;
|
|
In << GameRelativePath;
|
|
In << RootDirectories;
|
|
In << bIsStreamingRequest;
|
|
|
|
ConnectedPlatformName = TEXT("");
|
|
|
|
bool bSendLowerCase = false;
|
|
|
|
// if we didn't find one (and this is a dumb server - no active platforms), then just use what was sent
|
|
if (ActiveTargetPlatforms.Num() == 0)
|
|
{
|
|
ConnectedPlatformName = TargetPlatformNames[0];
|
|
}
|
|
// we only need to care about validating the connected platform if there are active targetplatforms
|
|
else
|
|
{
|
|
// figure out the best matching target platform for the set of valid ones
|
|
for (int32 TPIndex = 0; TPIndex < TargetPlatformNames.Num() && ConnectedPlatformName == TEXT(""); TPIndex++)
|
|
{
|
|
UE_LOG(LogFileServer, Display, TEXT(" Possible Target Platform from client: %s"), *TargetPlatformNames[TPIndex]);
|
|
|
|
// look for a matching target platform
|
|
for (int32 ActiveTPIndex = 0; ActiveTPIndex < ActiveTargetPlatforms.Num(); ActiveTPIndex++)
|
|
{
|
|
UE_LOG(LogFileServer, Display, TEXT(" Checking against: %s"), *ActiveTargetPlatforms[ActiveTPIndex]->PlatformName());
|
|
if (ActiveTargetPlatforms[ActiveTPIndex]->PlatformName() == TargetPlatformNames[TPIndex])
|
|
{
|
|
bSendLowerCase = ActiveTargetPlatforms[ActiveTPIndex]->SendLowerCaseFilePaths();
|
|
ConnectedPlatformName = ActiveTargetPlatforms[ActiveTPIndex]->PlatformName();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we didn't find one, reject client and also print some warnings
|
|
if (ConnectedPlatformName == TEXT(""))
|
|
{
|
|
// reject client we can't cook/compile shaders for you!
|
|
UE_LOG(LogFileServer, Warning, TEXT("Unable to find target platform for client, terminating client connection!"));
|
|
|
|
for (int32 TPIndex = 0; TPIndex < TargetPlatformNames.Num() && ConnectedPlatformName == TEXT(""); TPIndex++)
|
|
{
|
|
UE_LOG(LogFileServer, Warning, TEXT(" Target platforms from client: %s"), *TargetPlatformNames[TPIndex]);
|
|
}
|
|
for (int32 ActiveTPIndex = 0; ActiveTPIndex < ActiveTargetPlatforms.Num(); ActiveTPIndex++)
|
|
{
|
|
UE_LOG(LogFileServer, Warning, TEXT(" Active target platforms on server: %s"), *ActiveTargetPlatforms[ActiveTPIndex]->PlatformName());
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ConnectedEngineDir = EngineRelativePath;
|
|
ConnectedGameDir = GameRelativePath;
|
|
|
|
|
|
FString LocalEngineDir = FPaths::EngineDir();
|
|
FString LocalGameDir = FPaths::GameDir();
|
|
if ( FPaths::IsProjectFilePathSet() )
|
|
{
|
|
LocalGameDir = FPaths::GetPath(FPaths::GetProjectFilePath()) + TEXT("/");
|
|
}
|
|
|
|
UE_LOG(LogFileServer, Display, TEXT(" Connected EngineDir = %s"), *ConnectedEngineDir);
|
|
UE_LOG(LogFileServer, Display, TEXT(" Local EngineDir = %s"), *LocalEngineDir);
|
|
UE_LOG(LogFileServer, Display, TEXT(" Connected GameDir = %s"), *ConnectedGameDir);
|
|
UE_LOG(LogFileServer, Display, TEXT(" Local GameDir = %s"), *LocalGameDir);
|
|
|
|
// Remap the root directories requested...
|
|
for (int32 RootDirIdx = 0; RootDirIdx < RootDirectories.Num(); RootDirIdx++)
|
|
{
|
|
FString CheckRootDir = RootDirectories[RootDirIdx];
|
|
ConvertClientFilenameToServerFilename(CheckRootDir);
|
|
RootDirectories[RootDirIdx] = CheckRootDir;
|
|
}
|
|
|
|
// figure out the sandbox directory
|
|
// @todo: This should use FPlatformMisc::SavedDirectory(GameName)
|
|
FString SandboxDirectory;
|
|
if ( FPaths::IsProjectFilePathSet() )
|
|
{
|
|
FString ProjectDir = FPaths::GetPath(FPaths::GetProjectFilePath());
|
|
SandboxDirectory = FPaths::Combine(*ProjectDir, TEXT("Saved"), TEXT("Cooked"), *ConnectedPlatformName);
|
|
if( bIsStreamingRequest )
|
|
{
|
|
RootDirectories.Add(ProjectDir);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (FPaths::GetExtension(GameName) == FProjectDescriptor::GetExtension())
|
|
{
|
|
SandboxDirectory = FPaths::Combine(*FPaths::GetPath(GameName), TEXT("Saved"), TEXT("Cooked"), *ConnectedPlatformName);
|
|
}
|
|
else
|
|
{
|
|
//@todo: This assumes the game is located in the UE4 Root directory
|
|
SandboxDirectory = FPaths::Combine(*FPaths::GetRelativePathToRoot(), *GameName, TEXT("Saved"), TEXT("Cooked"), *ConnectedPlatformName);
|
|
}
|
|
}
|
|
// Convert to full path so that the sandbox wrapper doesn't re-base to Saved/Sandboxes
|
|
SandboxDirectory = FPaths::ConvertRelativePathToFull(SandboxDirectory);
|
|
|
|
// delete any existing one first, in case game name somehow changed and client is re-asking for files (highly unlikely)
|
|
delete Sandbox;
|
|
Sandbox = new FSandboxPlatformFile(false);
|
|
Sandbox->Initialize(&FPlatformFileManager::Get().GetPlatformFile(), *FString::Printf(TEXT("-sandbox=\"%s\""), *SandboxDirectory));
|
|
|
|
// make sure the global shaders are up to date before letting the client read any shaders
|
|
// @todo: This will probably add about 1/2 second to the boot-up time of the client while the server does this
|
|
// @note: We assume the delegate will write to the proper sandbox directory, should we pass in SandboxDirectory, or Sandbox?
|
|
FShaderRecompileData RecompileData;
|
|
RecompileData.PlatformName = ConnectedPlatformName;
|
|
// All target platforms
|
|
RecompileData.ShaderPlatform = -1;
|
|
RecompileData.ModifiedFiles = NULL;
|
|
RecompileData.MeshMaterialMaps = NULL;
|
|
RecompileShadersDelegate.ExecuteIfBound(RecompileData);
|
|
|
|
UE_LOG(LogFileServer, Display, TEXT("Getting files for %d directories, game = %s, platform = %s"), RootDirectories.Num(), *GameName, *ConnectedPlatformName);
|
|
UE_LOG(LogFileServer, Display, TEXT(" Sandbox dir = %s"), *SandboxDirectory);
|
|
|
|
for (int32 DumpIdx = 0; DumpIdx < RootDirectories.Num(); DumpIdx++)
|
|
{
|
|
UE_LOG(LogFileServer, Display, TEXT("\t%s"), *(RootDirectories[DumpIdx]));
|
|
}
|
|
|
|
TArray<FString> DirectoriesToAlwaysStageAsUFS;
|
|
if ( GConfig->GetArray(TEXT("/Script/UnrealEd.ProjectPackagingSettings"), TEXT("DirectoriesToAlwaysStageAsUFS"), DirectoriesToAlwaysStageAsUFS, GGameIni) )
|
|
{
|
|
for ( const auto& DirectoryToAlwaysStage : DirectoriesToAlwaysStageAsUFS )
|
|
{
|
|
RootDirectories.Add( DirectoryToAlwaysStage );
|
|
}
|
|
}
|
|
|
|
// list of directories to skip
|
|
TArray<FString> DirectoriesToSkip;
|
|
TArray<FString> DirectoriesToNotRecurse;
|
|
// @todo: This should really be FPlatformMisc::GetSavedDirForGame(ClientGameName), etc
|
|
for (int32 DirIndex = 0; DirIndex < RootDirectories.Num(); DirIndex++)
|
|
{
|
|
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Saved/Backup")));
|
|
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Saved/Config")));
|
|
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Saved/Logs")));
|
|
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Saved/Sandboxes")));
|
|
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Saved/Cooked")));
|
|
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Saved/ShaderDebugInfo")));
|
|
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Saved/StagedBuilds")));
|
|
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Intermediate")));
|
|
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Documentation")));
|
|
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Extras")));
|
|
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Binaries")));
|
|
DirectoriesToSkip.Add(FString(RootDirectories[DirIndex] / TEXT("Source")));
|
|
DirectoriesToNotRecurse.Add(FString(RootDirectories[DirIndex] / TEXT("DerivedDataCache")));
|
|
}
|
|
|
|
// use the timestamp grabbing visitor (include directories)
|
|
FLocalTimestampDirectoryVisitor Visitor(*Sandbox, DirectoriesToSkip, DirectoriesToNotRecurse, true);
|
|
for (int32 DirIndex = 0; DirIndex < RootDirectories.Num(); DirIndex++)
|
|
{
|
|
Sandbox->IterateDirectory(*RootDirectories[DirIndex], Visitor);
|
|
}
|
|
|
|
// report the package version information
|
|
// The downside of this is that ALL cooked data will get tossed on package version changes
|
|
int32 PackageFileUE4Version = GPackageFileUE4Version;
|
|
Out << PackageFileUE4Version;
|
|
int32 PackageFileLicenseeUE4Version = GPackageFileLicenseeUE4Version;
|
|
Out << PackageFileLicenseeUE4Version;
|
|
|
|
// Send *our* engine and game dirs
|
|
Out << LocalEngineDir;
|
|
Out << LocalGameDir;
|
|
|
|
// return the files and their timestamps
|
|
TMap<FString, FDateTime> FixedTimes = FixupSandboxPathsForClient(Sandbox, Visitor.FileTimes, LocalEngineDir, LocalGameDir, bSendLowerCase);
|
|
Out << FixedTimes;
|
|
|
|
// Do it again, preventing access to non-cooked files
|
|
if( bIsStreamingRequest == false )
|
|
{
|
|
TArray<FString> RootContentPaths;
|
|
FPackageName::QueryRootContentPaths(RootContentPaths);
|
|
TArray<FString> ContentFolders;
|
|
for (const auto& RootPath : RootContentPaths)
|
|
{
|
|
const FString& ContentFolder = FPackageName::LongPackageNameToFilename(RootPath);
|
|
|
|
FString ConnectedContentFolder = ContentFolder;
|
|
ConnectedContentFolder.ReplaceInline(*LocalEngineDir, *ConnectedEngineDir);
|
|
|
|
int32 ReplaceCount = 0;
|
|
|
|
// If one path is relative and the other isn't, convert both to absolute paths before trying to replace
|
|
if (FPaths::IsRelative(LocalGameDir) != FPaths::IsRelative(ConnectedContentFolder))
|
|
{
|
|
FString AbsoluteLocalGameDir = FPaths::ConvertRelativePathToFull(LocalGameDir);
|
|
FString AbsoluteConnectedContentFolder = FPaths::ConvertRelativePathToFull(ConnectedContentFolder);
|
|
ReplaceCount = AbsoluteConnectedContentFolder.ReplaceInline(*AbsoluteLocalGameDir, *ConnectedGameDir);
|
|
if (ReplaceCount > 0)
|
|
{
|
|
ConnectedContentFolder = AbsoluteConnectedContentFolder;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ReplaceCount = ConnectedContentFolder.ReplaceInline(*LocalGameDir, *ConnectedGameDir);
|
|
}
|
|
|
|
if (ReplaceCount == 0)
|
|
{
|
|
int32 GameDirOffset = ConnectedContentFolder.Find(ConnectedGameDir, ESearchCase::IgnoreCase, ESearchDir::FromEnd);
|
|
if (GameDirOffset != INDEX_NONE)
|
|
{
|
|
ConnectedContentFolder = ConnectedContentFolder.RightChop(GameDirOffset);
|
|
}
|
|
}
|
|
|
|
ContentFolders.Add(ConnectedContentFolder);
|
|
}
|
|
Out << ContentFolders;
|
|
|
|
// Do it again, preventing access to non-cooked files
|
|
const int32 NUM_EXCLUSION_WILDCARDS = 2;
|
|
FString ExclusionWildcard[NUM_EXCLUSION_WILDCARDS];
|
|
ExclusionWildcard[0] = FString(TEXT("*")) + FPackageName::GetAssetPackageExtension();
|
|
ExclusionWildcard[1] = FString(TEXT("*")) + FPackageName::GetMapPackageExtension();
|
|
|
|
for (int32 i=0; i < NUM_EXCLUSION_WILDCARDS; ++i)
|
|
{
|
|
Sandbox->AddExclusion(*ExclusionWildcard[i]);
|
|
UE_LOG(LogFileServer, Display, TEXT("Excluding %s from non-sandboxed directories"),
|
|
*ExclusionWildcard[i]);
|
|
}
|
|
|
|
FLocalTimestampDirectoryVisitor VisitorForCacheDates(*Sandbox, DirectoriesToSkip, DirectoriesToNotRecurse, true);
|
|
|
|
for (int32 DirIndex = 0; DirIndex < RootDirectories.Num(); DirIndex++)
|
|
{
|
|
Sandbox->IterateDirectory(*RootDirectories[DirIndex], VisitorForCacheDates);
|
|
}
|
|
|
|
// return the cached files and their timestamps
|
|
FixedTimes = FixupSandboxPathsForClient(Sandbox, VisitorForCacheDates.FileTimes, LocalEngineDir, LocalGameDir, bSendLowerCase);
|
|
Out << FixedTimes;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessHeartbeat( FArchive& In, FArchive& Out )
|
|
{
|
|
// Protect the array
|
|
FScopeLock Lock(&ModifiedFilesSection);
|
|
|
|
// return the list of modified files
|
|
Out << ModifiedFiles;
|
|
|
|
// @todo: note the last received time, and toss clients that don't heartbeat enough!
|
|
|
|
// @todo: Right now, there is no directory watcher adding to ModifiedFiles. It had to be pulled from this thread (well, the ModuleManager part)
|
|
// We should have a single directory watcher that pushes the changes to all the connections - or possibly pass in a shared DirectoryWatcher
|
|
// and have each connection set up a delegate (see p4 history for HandleDirectoryWatcherDirectoryChanged)
|
|
}
|
|
|
|
|
|
/* FStreamingNetworkFileServerConnection callbacks
|
|
*****************************************************************************/
|
|
|
|
bool FNetworkFileServerClientConnection::PackageFile( FString& Filename, FArchive& Out )
|
|
{
|
|
// get file timestamp and send it to client
|
|
FDateTime ServerTimeStamp = Sandbox->GetTimeStamp(*Filename);
|
|
|
|
TArray<uint8> Contents;
|
|
// open file
|
|
IFileHandle* File = Sandbox->OpenRead(*Filename);
|
|
|
|
if (!File)
|
|
{
|
|
ServerTimeStamp = FDateTime::MinValue(); // if this was a directory, this will make sure it is not confused with a zero byte file
|
|
|
|
UE_LOG(LogFileServer, Warning, TEXT("Request for missing file %s."), *Filename );
|
|
}
|
|
else
|
|
{
|
|
if (!File->Size())
|
|
{
|
|
UE_LOG(LogFileServer, Warning, TEXT("Sending empty file %s...."), *Filename);
|
|
}
|
|
else
|
|
{
|
|
// read it
|
|
Contents.AddUninitialized(File->Size());
|
|
File->Read(Contents.GetData(), Contents.Num());
|
|
}
|
|
|
|
// close it
|
|
delete File;
|
|
|
|
UE_LOG(LogFileServer, Display, TEXT("Read %s, %d bytes"), *Filename, Contents.Num());
|
|
}
|
|
|
|
Out << Filename;
|
|
Out << ServerTimeStamp;
|
|
uint64 FileSize = Contents.Num();
|
|
Out << FileSize;
|
|
Out.Serialize(Contents.GetData(), FileSize);
|
|
return true;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessRecompileShaders( FArchive& In, FArchive& Out )
|
|
{
|
|
TArray<FString> RecompileModifiedFiles;
|
|
TArray<uint8> MeshMaterialMaps;
|
|
FShaderRecompileData RecompileData;
|
|
RecompileData.PlatformName = ConnectedPlatformName;
|
|
RecompileData.ModifiedFiles = &RecompileModifiedFiles;
|
|
RecompileData.MeshMaterialMaps = &MeshMaterialMaps;
|
|
|
|
// tell other side all the materials to load, by pathname
|
|
In << RecompileData.MaterialsToLoad;
|
|
In << RecompileData.ShaderPlatform;
|
|
In << RecompileData.SerializedShaderResources;
|
|
In << RecompileData.bCompileChangedShaders;
|
|
|
|
RecompileShadersDelegate.ExecuteIfBound(RecompileData);
|
|
|
|
// tell other side what to do!
|
|
Out << RecompileModifiedFiles;
|
|
Out << MeshMaterialMaps;
|
|
}
|
|
|
|
|
|
void FNetworkFileServerClientConnection::ProcessSyncFile( FArchive& In, FArchive& Out )
|
|
{
|
|
// get filename
|
|
FString Filename;
|
|
In << Filename;
|
|
|
|
ConvertClientFilenameToServerFilename(Filename);
|
|
|
|
//FString AbsFile(FString(*Sandbox->ConvertToAbsolutePathForExternalApp(*Filename)).MakeStandardFilename());
|
|
// ^^ we probably in general want that filename, but for cook on the fly, we want the un-sandboxed name
|
|
|
|
TArray<FString> NewUnsolictedFiles;
|
|
|
|
FileRequestDelegate.ExecuteIfBound(Filename, ConnectedPlatformName, NewUnsolictedFiles);
|
|
|
|
for (int32 Index = 0; Index < NewUnsolictedFiles.Num(); Index++)
|
|
{
|
|
if (NewUnsolictedFiles[Index] != Filename)
|
|
{
|
|
UnsolictedFiles.AddUnique(NewUnsolictedFiles[Index]);
|
|
}
|
|
}
|
|
|
|
PackageFile(Filename, Out);
|
|
}
|
|
FString FNetworkFileServerClientConnection::GetDescription() const
|
|
{
|
|
return FString("Client For " ) + ConnectedPlatformName;
|
|
}
|
|
|
|
|