Files
UnrealEngineUWP/Engine/Source/Runtime/NetworkFileSystem/Private/NetworkFileServerConnection.cpp
Ben Marsh aa969f9931 Copying //UE4/Dev-Core to //UE4/Dev-Main (Source: //UE4/Dev-Core @ 3420477)
#lockdown Nick.Penwarden
#rb none

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3386262 on 2017/04/10 by Ben.Marsh

	Add app-local deployment of DirectX components that are no longer included with newer versions of Windows by default (XAudio 2.7, XInput 1.3). Also add a one-click button to the packaging settings to include the default app-local dependencies, rather than having to specify the path.

Change 3386999 on 2017/04/10 by Ben.Marsh

	Plugins: Add support for explicit dependencies from one plugin onto another. Required plugins can be configured in an identical manner to project files, by adding a "Plugins" key to the .uplugin file. Dependencies are automatically built and loaded, and the plugin browser will warn if you try to disable a plugin that something else has a dependency on.

Change 3387073 on 2017/04/10 by Ben.Marsh

	Move FLightPropagationRuntimeSettings into the Renderer module, to remove engine dependency on a plugin.

Change 3387988 on 2017/04/11 by Steve.Robb

	Comments added to clarify the role of DestructItem and DestructItems.

Change 3388085 on 2017/04/11 by Ben.Marsh

	UBT: Fix bEnabled flag on plugin references being ignored. Now collect up all the plugin references in order of priority before creating plugin instances for them. Fixes CIS fail for UT.

Change 3390048 on 2017/04/12 by Richard.Hinckley

	#jira UE-43876
	Fixed description of Streaming settings (within Project Settings).

Change 3390697 on 2017/04/12 by Steve.Robb

	CLASS_PointersDefaultToAutoWeak and CLASS_PointersDefaultToWeak removed.

Change 3390711 on 2017/04/12 by Steve.Robb

	AGRESSIVE_ARRAY_FORCEINLINE removed.

Change 3392167 on 2017/04/13 by Robert.Manuszewski

	UObject can be added to GC cluster only if all of its Outers can also be added to it.

	Fixing asserts caused by components that are added to GC clusters even if their owner actors that can't be in GC clusters.

	#jira UE-42948

Change 3392309 on 2017/04/13 by Robert.Manuszewski

	When adding objects to clusters after these clusters have been created it's possible to come across objects that are already in the cluster we're adding the object to so instead of crashing, allow it.

Change 3392620 on 2017/04/13 by Ben.Marsh

	UGS: Only check for updates every 5 minutes.

Change 3392623 on 2017/04/13 by Ben.Marsh

	UGS: Only poll for new changes every 60 seconds.

Change 3392744 on 2017/04/13 by Ben.Marsh

	UGS: Query changelist descriptions individually to determine whether changes affect code or content, to hopefully reduce Perforce server load.

Change 3392874 on 2017/04/13 by Ben.Marsh

	UGS: Allow specifying regexes in the project config file which filters which changes to be displayed. Useful for changes submitted by build machines, updates to collections, etc...

Change 3392878 on 2017/04/13 by Ben.Marsh

	Update UGS to version 1.96

Change 3395635 on 2017/04/17 by Ben.Marsh

	UAT: Prefix log output from executing UAT commands through BuildGraph with the name of that command.

Change 3395655 on 2017/04/17 by Ben.Marsh

	UAT: Add a command for syncing a DDC over the network (SyncDDC). Allows specifying a maximum size to copy, number of days worth of modified files to copy, and time limit not to be exceeded.

Change 3396989 on 2017/04/17 by Wes.Hunt

	CrashReporter configurable tweaks.
	* Added QueueWaitingTimeAlertThreshold (used to be hardcoded to 1 min).
	  - When the queue waiting time gets beyond this many seconds, trigger a slack alert message. Default is 10 min.
	  - Zero means never alert.
	* Added DiskSpaceAvailableAlertInterval (used to be hardcoded to 1 day).
	  - Interval by which to report disk space availability.
	  - Default is never (Zero)
	* Updated config file to match production config.
	#codereview:jin.zhang

Change 3397656 on 2017/04/18 by Ben.Marsh

	UBT: Allow modules to opt-out of getting the default include paths from being added, by setting bAddDefaultIncludePaths = false from their build.cs file.

Change 3397677 on 2017/04/18 by Robert.Manuszewski

	PR #3167 : Adding more descriptive error text to DetatchLinker error check (by rooneym)


Change 3397722 on 2017/04/18 by Robert.Manuszewski

	PR #2252: Increase linker reporting for failed imports (Contributed by FineRedMist)


Change 3397739 on 2017/04/18 by Richard.Hinckley

	#jira UE-44100
	Fixed SanitizePackageName() to remove double-slash, triple-slash, etc. from package names. Also updated CreatePackage() to call SanitizePackageName() before creating.

Change 3398023 on 2017/04/18 by Ben.Marsh

	PR #3105: Cook/package with editor and debugger attached (Contributed by projectgheist)


Change 3398095 on 2017/04/18 by Ben.Marsh

	PR #3051: Generate map file from UAT (Contributed by projectgheist)


Change 3398212 on 2017/04/18 by Ben.Marsh

	PR #2915: UE-38232: Removed duplicate stats (Contributed by projectgheist)


Change 3399304 on 2017/04/19 by Ben.Marsh

	UGS: Prevent editor target files being removed when running custom tools.

Change 3399306 on 2017/04/19 by Robert.Manuszewski

	Moved InitPropertiesFromCustomList to UbLueprintGeneratedClass and made it thread safe

Change 3399729 on 2017/04/19 by Steve.Robb

	Simple optimization to TBitArray::RemoveAt() when all removed bits are at the end of the array.
	RemoveAtSwap() now simply decrements the count instead of calling RemoveAt().
	Checks for a positive count added to RemoveAt() and RemoveAtSwap().

Change 3399750 on 2017/04/19 by Jin.Zhang

	Order branch alphabetically #RB

Change 3400186 on 2017/04/19 by Steve.Robb

	Per-header generated code.

Change 3401458 on 2017/04/20 by Steve.Robb

	Static log categories moved out of headers to prevent duplicates when the header is included multiple times.

	#jira UE-37507

Change 3401657 on 2017/04/20 by Gil.Gribb

	UE4 - Simplified and reworked lock free lists and the task graph bringing all platforms under the same scheme.

Change 3401735 on 2017/04/20 by Gil.Gribb

	UE4 - Updated apple platform atomics with a new clang version which is intended to be shared among all clang platforms.

Change 3403362 on 2017/04/21 by Steve.Robb

	Algo::Sort() fixed to support C arrays.
	Size+count versions of Also::IsSorted() deprecated.
	Algo::IsSortedBy() added.
	Algo::FindBy() added to allow an element to be found by projection.
	Simplifications and generalizations.

Change 3404017 on 2017/04/21 by Ben.Marsh

	Fix issue where referenced plugin descriptors were missing from console builds, and prevent monolithic builds from offering to disable missing plugins.

Change 3405299 on 2017/04/24 by Steve.Robb

	Clarified the class of the incompatible function in the error message about incompatible BP event specifiers.

	#jira UE-35106

Change 3405302 on 2017/04/24 by Ben.Marsh

	UBT: Allow excluding documentation from generated project files, by setting <ProjectFileGenerator><bIncludeDocumentation>false</bIncludeDocumentation></ProjectFileGenerator> in the XML configuration file.

Change 3405629 on 2017/04/24 by Ben.Marsh

	Rename CPPEnvironment to CppCompileEnvironment, to reflect the class name.

Change 3406431 on 2017/04/24 by Ben.Marsh

	UAT: Fix incorrect handling of P4SubmitOptions when multiple values are present.

Change 3406670 on 2017/04/24 by Ben.Marsh

	UBT: Enable warnings for classes with virtual functions and no virtual destructor (C4265 on Windows, -fdelete-non-virtual-dtor on Clang).

Change 3407080 on 2017/04/25 by Gil.Gribb

	UE4 - Critical fix: Propoerly disambiguate imports with the same name and the same outer name. This fixes an assert: LocalExportIndex.IsNull.

Change 3407486 on 2017/04/25 by Gil.Gribb

	UE4 - Made changes so that servers, programs and non-engine executables do not create background or high priority threads.

Change 3407495 on 2017/04/25 by Gil.Gribb

	UE4 - Tweaked out XBox and Windows low level file IO.

Change 3407497 on 2017/04/25 by Gil.Gribb

	UE4 - Fixed bug in the pak precacher that would result in blocks being discarded too soon, which, in turn, resulted in redudnant reads.

Change 3407705 on 2017/04/25 by Ben.Marsh

	Removing most of the junk in DotNETUtilities.

Change 3409701 on 2017/04/26 by Ben.Marsh

	Disable another static analyzer warning for third party libraries.

Change 3410074 on 2017/04/26 by Daniel.Lamb

	Network platform file runs heart beats and responds to modified file changes.
	Cook on the fly server in the editor (COTS) now detects changes to content and notifies client.
	Fixed issue with network platform file not using correct sandbox.

	#test cook on the side shootergame

Change 3411131 on 2017/04/27 by Steve.Robb

	TIsTriviallyDestructible now supports forward-declared enums.

Change 3411186 on 2017/04/27 by Steve.Robb

	Fix for #includes in generated code for Within classes which are in a different module from the generated class.

Change 3411917 on 2017/04/27 by Steve.Robb

	Fixes to pushing/popping the CPP macro.

Change 3411966 on 2017/04/27 by Steve.Robb

	Include spam reduced in generated code.

Change 3412155 on 2017/04/27 by Ben.Marsh

	Fix for PVS Studio warning: VFOVInRadians used instead of HFOVInRadians.

Change 3412223 on 2017/04/27 by Ben.Marsh

	Fix for PVS-Studio warning: Calling SetHelperA.Num() twice.

Change 3412273 on 2017/04/27 by Ben.Marsh

	Fix for PVS-Studio warning: Duplicated variable name.

Change 3412511 on 2017/04/27 by Ben.Marsh

	PR #3462: Fixed PVS-Studio issues (Part 1) (Contributed by PaulEremeeff)


Change 3412582 on 2017/04/27 by Ben.Marsh

	Fix for PVS-Studio warning: Incorrect variable name in copy/pasted code

Change 3413136 on 2017/04/28 by Robert.Manuszewski

	Helper functions for dissolving specific GC clusters

Change 3413310 on 2017/04/28 by Ben.Marsh

	Fix for PVS-Studio warning: Incorrect variable name in copy/pasted code.

Change 3413341 on 2017/04/28 by Gil.Gribb

	UE4 - Add prestream capability to allow us to preload always loaded sublevels. Only turned on for Shootergame.

Change 3413351 on 2017/04/28 by Ben.Marsh

	Include code analysis macros directly from Platform.h, so that macros are available to everything.

Change 3413352 on 2017/04/28 by Ben.Marsh

	Fixing a few more PVS studio warnings.

Change 3413437 on 2017/04/28 by Ben.Marsh

	Fix for PVS-Studio warning: Comparison is always true.

Change 3413759 on 2017/04/28 by Ben.Marsh

	Suppressing warnings for PVS-Studio.

Change 3413784 on 2017/04/28 by Ben.Marsh

	Fix PVS-Studio warning.

Change 3413898 on 2017/04/28 by Ben.Marsh

	Fix PVS-Studio warning: Same conditional is checked twice.

Change 3413915 on 2017/04/28 by Ben.Marsh

	Fix PVS-Studio warning: LHS of expression is identical to RHS.

Change 3413989 on 2017/04/28 by Ben.Marsh

	Fix for PVS-Studio warning: If CurrentGraph->SubGraphs.Num() == 1, it will always enter the first conditional block.

Change 3414053 on 2017/04/28 by Ben.Marsh

	More PVS-Studio fixes.

Change 3414062 on 2017/04/28 by Ben.Marsh

	Fix for PVS-Studio warning: Pointer to object goes out of scope without being freed.

Change 3414070 on 2017/04/28 by Ben.Marsh

	Fix for PVS-Studio warning: Fix incorrect condition.

Change 3414071 on 2017/04/28 by Ben.Marsh

	Fix for PVS-Studio warning: Array index is always zero.

Change 3414116 on 2017/04/28 by Ben.Marsh

	BuildGraph: Allow marking compile tasks as unsuitable for use with the parallel executor, via an AllowParallelExecutor="false" attribute.

Change 3414160 on 2017/04/28 by Ben.Marsh

	Add support for running PVS-Studio through UnrealBuildTool. To use, pass -StaticAnalyzer=PVSStudio to the build command line (similarly, the Visual C++ analyzer can now be invoked using -StaticAnalyzer=VisualCpp). A log file will be written to the Engine/Saved/PVS-Studio or <Project>/Saved/PVS-Studio directory containing diagnostics, which can be opened using the "unparsed output" filter in the PVS-Studio standalone application. High priority warnings are printed to stdout.

Change 3414237 on 2017/04/28 by Ben.Marsh

	EC: Allow disabling and enabling the log preprocessor via special markers in the log.

	To disable: <-- Suspend Log Parsing -->
	To enable: <-- Resume Log Parsing -->

Change 3414343 on 2017/04/28 by Ben.Marsh

	UBT: Exclude ThirdParty folders from PVS output.


Change 3414392 on 2017/04/28 by Ben.Marsh

	Fix regular strings being casted to BSTRs; BSTRs have a hidden length prefix in the two bytes before the first character, so passing a regular TCHAR* is reading random memory.

Change 3414459 on 2017/04/28 by Ben.Marsh

	Fix for PVS-Studio warning: Object goes out of scope without being freed.

Change 3414495 on 2017/04/28 by Ben.Marsh

	Suppress some more PVS-Studio warnings.

Change 3414514 on 2017/04/28 by Ben.Marsh

	Fix for PVS-Studio warning: Testing WorldType being equal to EditorPreview and not equal to Inactive is redundant; changing to match description in comment instead.

Change 3414526 on 2017/04/28 by Ben.Marsh

	Fix for PVS-Studio warning: Variable assigned to itself has no effect.

Change 3415183 on 2017/04/29 by Ben.Marsh

	Fix conflict in macro definitions for ENABLE_HTTP_FOR_NFS - rename the macro defined by NetworkFile to ENABLE_HTTP_FOR_NF. Hopefully fix CIS.

Change 3415765 on 2017/05/01 by Ben.Marsh

	Suppressing PVS-Studio warning to get things building cleanly. Not sure if FContentHelper is being leaked or not.

Change 3415853 on 2017/05/01 by Ben.Marsh

	EC: Fix jobs never completing if a "Sync & Build" step fails. Dependent jobs should evaluate their run conditions as soon as the parent step finishes, rather than waiting for child job steps to be created.

Change 3416138 on 2017/05/01 by Ben.Marsh

	Fix Fortnite cook failures. Not sure what the exact problem is here, but my hunch is that discarded "const" causes blueprint compile failures due to not being able to connect output pins between nodes for overloaded functions, or something like that.

Change 3416309 on 2017/05/01 by Ben.Marsh

	Build: Fix node names for static analysis.

Change 3416360 on 2017/05/01 by Ben.Marsh

	UBT: Remove unused arguments to PrepForUATPackageOrDeploy for Windows.

Change 3416398 on 2017/05/01 by Daniel.Lamb

	Cook on the fly NetworkFileServerConnection Remove FileModifiedCallback delegate when the connection is closed.

	#test Cook on the side shootergame.

Change 3416826 on 2017/05/01 by Daniel.Lamb

	Added callback to game when files are requested reload from networkfileserver.
	Game will need to unload / reload effected objects.
	Working on simple reload capability in shootergame.

	#test Cook on the side shootergame with reloading

Change 3417983 on 2017/05/02 by Ben.Marsh

	EC: Remove warning for lines not matching p4 tag syntax when running preflights; multi-line descriptions in shelved changelists break this pattern.

Change 3418747 on 2017/05/02 by Steve.Robb

	Fix for const pointer properties.
	Fix for UHT debugging manifest.
	Test added for pointer properties.

Change 3420477 on 2017/05/03 by Gil.Gribb

	UE4 - Removed check from windows async IO layer.

[CL 3421020 by Ben Marsh in Main branch]
2017-05-03 14:18:32 -04:00

1108 lines
33 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "NetworkFileServerConnection.h"
#include "HAL/PlatformFilemanager.h"
#include "Misc/Paths.h"
#include "Misc/ScopeLock.h"
#include "Serialization/BufferArchive.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/LocalTimestampDirectoryVisitor.h"
#include "IPlatformFileSandboxWrapper.h"
#include "NetworkMessage.h"
#include "ProjectDescriptor.h"
#include "NetworkFileSystemLog.h"
#include "Misc/PackageName.h"
#include "Interfaces/ITargetPlatform.h"
/**
* Helper function for resolving engine and game sandbox paths
*/
void GetSandboxRootDirectories(FSandboxPlatformFile* Sandbox, FString& SandboxEngine, FString& SandboxGame, const FString& LocalEngineDir, const FString& LocalGameDir)
{
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
SandboxGame = Sandbox->ConvertToSandboxPath(*(LocalGameDir + TEXT("a.txt"))).Replace(TEXT("a.txt"), TEXT(""));
}
/* FNetworkFileServerClientConnection structors
*****************************************************************************/
FNetworkFileServerClientConnection::FNetworkFileServerClientConnection( const FFileRequestDelegate& InFileRequestDelegate,
const FRecompileShadersDelegate& InRecompileShadersDelegate, const FSandboxPathDelegate& InSandboxPathOverrideDelegate, FOnFileModifiedDelegate* InOnFileModifiedCallback, const TArray<ITargetPlatform*>& InActiveTargetPlatforms )
: LastHandleId(0)
, Sandbox(NULL)
, ActiveTargetPlatforms(InActiveTargetPlatforms)
{
OnFileModifiedCallback = InOnFileModifiedCallback;
if (OnFileModifiedCallback)
{
OnFileModifiedCallback->AddRaw(this, &FNetworkFileServerClientConnection::FileModifiedCallback);
}
if (InFileRequestDelegate.IsBound())
{
FileRequestDelegate = InFileRequestDelegate;
}
if (InRecompileShadersDelegate.IsBound())
{
RecompileShadersDelegate = InRecompileShadersDelegate;
}
if ( InSandboxPathOverrideDelegate.IsBound() )
{
SandboxPathOverrideDelegate = InSandboxPathOverrideDelegate;
}
}
FNetworkFileServerClientConnection::~FNetworkFileServerClientConnection( )
{
OnFileModifiedCallback->RemoveAll(this);
// 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.
*/
TMap<FString, FDateTime> FNetworkFileServerClientConnection::FixupSandboxPathsForClient(const TMap<FString, FDateTime>& SandboxPaths)
{
TMap<FString,FDateTime> FixedFiletimes;
// 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)
{
FixedFiletimes.Add(FixupSandboxPathForClient(It.Key()), It.Value());
}
return FixedFiletimes;
}
/**
* 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.
*/
FString FNetworkFileServerClientConnection::FixupSandboxPathForClient(const FString& Filename)
{
const FString& LocalEngineDir = FPaths::EngineDir();
const FString& LocalGameDir = FPaths::GameDir();
FString Fixed = Sandbox->ConvertToSandboxPath(*Filename);
Fixed = Fixed.Replace(*SandboxEngine, *LocalEngineDir);
Fixed = Fixed.Replace(*SandboxGame, *LocalGameDir);
if (bSendLowerCase)
{
Fixed = Fixed.ToLower();
}
return Fixed;
}
/*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("");
// 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 ( SandboxPathOverrideDelegate.IsBound() )
{
SandboxDirectory = SandboxPathOverrideDelegate.Execute();
// if the sandbox directory delegate returns a path with the platform name in it then replace it :)
SandboxDirectory.ReplaceInline(TEXT("[Platform]"), *ConnectedPlatformName);
}
else 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));
GetSandboxRootDirectories(Sandbox, SandboxEngine, SandboxGame, LocalEngineDir, LocalGameDir);
// 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(Visitor.FileTimes);
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(VisitorForCacheDates.FileTimes);
Out << FixedTimes;
}
return true;
}
void FNetworkFileServerClientConnection::FileModifiedCallback( const FString& Filename)
{
FScopeLock Lock(&ModifiedFilesSection);
// do we care about this file???
// translation here?
ModifiedFiles.Add(Filename);
}
void FNetworkFileServerClientConnection::ProcessHeartbeat( FArchive& In, FArchive& Out )
{
TArray<FString> FixedupModifiedFiles;
// Protect the array
{
FScopeLock Lock(&ModifiedFilesSection);
for (const auto& ModifiedFile : ModifiedFiles)
{
FixedupModifiedFiles.Add(FixupSandboxPathForClient(ModifiedFile));
}
ModifiedFiles.Empty();
}
// return the list of modified files
Out << FixedupModifiedFiles;
// @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
}
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;
}