Files
UnrealEngineUWP/Engine/Source/Runtime/NetworkFileSystem/Private/NetworkFileServerConnection.cpp
Ben Marsh 6c32244d56 Copying //UE4/Release-Staging-4.11 to //UE4/Main (Source: //UE4/Release-Staging-4.11 @ 2941426, //UE4/Release-4.11 @ 2927265)
==========================
MAJOR FEATURES + CHANGES
==========================

Change 2910079 on 2016/03/15 by Taizyd.Korambayil

	#jira UE-28293 Reworded some Sentences

Change 2910157 on 2016/03/15 by Taizyd.Korambayil

	#jira UE-28240 Rebuilt Lighting for Sanctuary

Change 2910317 on 2016/03/15 by Ben.Marsh

	Fix crash trying to print out a message explaining that you need to install the Visual Studio 2015 toolchain, if the Visual Studio 2015 toolchain is not installed!

Change 2910425 on 2016/03/15 by Ori.Cohen

	Fix crash and incorrect behavior when setting physical material on a welded body.

	#JIRA UE-28399
	#rb Marc.Audy

Change 2910525 on 2016/03/15 by Ori.Cohen

	Fix player capsule not spawning at the right place due to float precision issues.

	#JIRA UE-28438
	#rb Zak.Middleton

Change 2910595 on 2016/03/15 by Chris.Babcock

	Fixed issue with missing event location paired with IE_Pressed if IE_DoubleClick generated
	#jira UE-28051
	#ue4
	#codereview Marc.Audy

Change 2911442 on 2016/03/16 by Andrew.Rodham

	Sequencer: Fixed frame grabbers where hardware mapped surfaces to memory of a different stride

	#jira UE-28434

Change 2911596 on 2016/03/16 by andrew.porter

	Test content for blueprint vertex painting
	#jira UE-24473

Change 2911860 on 2016/03/16 by Jamie.Dale

	Allowed SViewport to (once again) be able to use non-pre-multiplied alpha blending

	SViewport now has an PreMultipliedAlpha argument (default true), which can control whether to use pre-multiplied alpha when blending is enabled (blending is disabled by default). Note: This is a change in behavior from 4.10, as non-pre-multiplied alpha blending used to be the default, but pre-multiplied alpha blending better supports the pipeline used through Slate.

	This change also cleans up the use of bool parameters in the FSlateDrawElement::MakeX functions to control the render behavior, instead favoring use of advanced ESlateDrawEffect flags.

	API Breaking Changes
	  - FSlateDrawElement::MakeGradient no longer takes a bInGammaCorrect bool, instead pass ESlateDrawEffect::NoGamma as part of InDrawEffects to disable gamma correction.
	  - FSlateDrawElement::MakeViewport no longer takes a bInGammaCorrect bool, instead pass ESlateDrawEffect::NoGamma as part of InDrawEffects to disable gamma correction.
	  - FSlateDrawElement::MakeViewport no longer takes a bInAllowBlending bool, instead pass ESlateDrawEffect::NoBlending as part of InDrawEffects to disable blending.

	#jira UE-26797

Change 2912345 on 2016/03/16 by Olaf.Piesche

	Removing the check that causes UE-28441, duplicating beam type data module from highest LOD in Cascade causes crash. The beam data module is the only one that explicitly checks to make sure it's always shared across LOD levels; there's no obvious reasons why duplicating beam data modules shouldn't be possible.

	#codereview simon.tovey
	#jira UE-28441

Change 2912526 on 2016/03/16 by Steve.Robb

	Fix uninitialized variables.

	#codereview robert.manuszewski
	#jira UE-28391

Change 2913114 on 2016/03/17 by Steve.Robb

	Fixed some private properties which caused UHT errors.

	#codereview robert.manuszewski
	#jira UE-28059

Change 2913295 on 2016/03/17 by Richard.TalbotWatkin

	Replicated from Dev-Editor CL 2913224

	Disallow assets from being deleted if PIE is active. This prevents various troubles which can occur when PIE is referencing asset objects.
	#jira UE-12387 - [CrashReport] Crash when deleting assets needed for template
	#RB Nick.Darnell, Frank.Fella

Change 2913310 on 2016/03/17 by Nick.Shin

	merging from //UE4/Dev-Platform to //UE4/Release-4.11

	--- original commit CL: #2913300 message ---
	corrected VS 2015 websocket lib to look at the right offset

	it is currently a high risk change to just update the libwebsocket wholesale for  release-4.11.

	this change is the most minimum invasive change with a lot of deep analysis (details will be put in jira: # UEPLAT-1221).

	this fix will also be pushed up to release-4.11

	#jira UE-22166 - HTML5 Cook on the fly will launch and then close browser
	#jira UE-22513 - HTTP Network File System crashes randomly.
	#jira UE-28003 - Fail to QuickLaunch HTML5 through UnrealFrontEnd

Change 2913593 on 2016/03/17 by Mark.Satterthwaite

	For non-debug builds silence the warning about no deth/stencil when shader writes to depth in MetalRHI - the RHI implementation will create a temporary D/S buffer to cope but really this needs to be properly addressed elsewhere.
	#jira UE-28491

Change 2913655 on 2016/03/17 by Taizyd.Korambayil

	#jira UE-28492 Rebuilt Lighting For the Samples Listed

Change 2914025 on 2016/03/17 by Olaf.Piesche

	Make sure ST primitives are added to NST draw list if in shader complexity mode

	#codereview simon.tovey
	#jira UE-28471

Change 2914027 on 2016/03/17 by Nick.Shin

[CL 2941462 by Ben Marsh in Main branch]
2016-04-12 17:04:39 -04:00

1011 lines
30 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);
ConnectedContentFolder.ReplaceInline(*LocalGameDir, *ConnectedGameDir);
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;
}