Files
UnrealEngineUWP/Engine/Source/Runtime/NetworkFile/Private/NetworkPlatformFile.cpp
Josh Adams 660ab7f0c9 Copying //UE4/Dev-Platform to //UE4/Main
==========================
MAJOR FEATURES + CHANGES
==========================

Change 2719147 on 2015/10/07 by Mark.Satterthwaite

	Allow the shader cache to perform some precompilation synchronously on load before falling back to asynchronous compilation to balance load times against total time spent precompiling. Added a stat to the group that reports how long the precompile has been running until it completes so it is easier to track.

Change 2719182 on 2015/10/07 by Mark.Satterthwaite

	Refactor the ShaderCache's internal data structures and change the way we handle recording whether a particular predraw state has been submitted to try and make it more efficient.

Change 2719185 on 2015/10/07 by Mark.Satterthwaite

	Merging CL #2717701: Try and fix random crashes on Mac when manipulating bound-shader-states caused by ShaderCache potentially providing a bogus shader state pointer on exit from predraw.

Change 2719434 on 2015/10/07 by Mark.Satterthwaite

	Make sure that Mac ensures reports have a source context and a sane callstack when sent to the crash-reports server.

Change 2724764 on 2015/10/12 by Josh.Adams

	[Initial AppleTV support]
	Merging	//depot/YakBranch/... to //UE4/Dev-Platform/...

Change 2726266 on 2015/10/13 by Lee.Clark

	PS4 - Calc reserve size required for DMA copy when using unsafe command buffers

Change 2726401 on 2015/10/13 by Mark.Satterthwaite

	Merging CL #2716418: Fix UE-15228 'Crash Report Client doesn't restart into project editor on Mac' by reporting the original command line supplied by LaunchMac, not the modified one that strips the project name. The CRC can then relaunch as expected.
	#jira UE-15228

Change 2726421 on 2015/10/13 by Lee.Clark

	PS4 - Don't try to clear invalid targets

Change 2727040 on 2015/10/13 by Michael.Trepka

	Merging CL 2724777 - Fixed splash screen rendering for images with DPI different than 72

Change 2729783 on 2015/10/15 by Keith.Judge

	Fix huge memory leak in Test/Shipping configurations, caused because I am a numpty.

Change 2729847 on 2015/10/15 by Mark.Satterthwaite

	Merging CL #2729846: On OS X unconstrain windows from the dimension of the parent display when in Windowed mode - it is OK for them to be larger in this case. They do need to be repositioned if on the Primary display so that they don't creep under the menu bar and become unmovable/unclosable and Fullscreen windows still need to be constrained to a single display. We can now take screenshots of windows that are larger than the display & not get grey bars beyond the cutoff.
	#jira UE-21992

Change 2729865 on 2015/10/15 by Keith.Judge

	Fast semantics - Finish up resource transitions, adding resource decompression where appropriate and using non-fast clears where we can't determine the resource transition.

Change 2729897 on 2015/10/15 by Keith.Judge

	Fast Semantics - Make sure all GetData() calls are made safe with GPU fences.

Change 2729972 on 2015/10/15 by Keith.Judge

	Removed the last vestiges of ID3D11DeviceContext/ID3D11DeviceContext1 from the Xbox RHI. Everything now uses ID3D11DeviceContextX directly.

	This should be marginally quicker as it stops a double call to ClearState().

Change 2731503 on 2015/10/16 by Keith.Judge

	Added _XDK_VERSION to the DDC key for textures, which should solve the issue of the tiling mode changing in August XDK (and future changes Microsoft may inflict).

Change 2731596 on 2015/10/16 by Keith.Judge

	Fast Semantics - Add deferred resource deletion queue to make deleted resources be actually deleted a number of frames later so that the GPU is definitely finished with them. Hooked up the temporary SRVs for dynamic VBs as a first step.

Change 2731928 on 2015/10/16 by Michael.Trepka

	PR #1659: Mac/Build.sh handles additional arguments (Contributed by judgeaxl)

Change 2731934 on 2015/10/16 by Michael.Trepka

	PR #1618: added clang 3.7.0 -Wshift-negative-value ignore in JpegImageWrapper.cpp (Contributed by bsekura)

Change 2732018 on 2015/10/16 by Mark.Satterthwaite

	Emit a shader code cache for each platforms requested shader formats, this is separate to the targeted formats as not all can or need to be cached.
	- The implementation extends the ShaderCache's hooks in FShaderResource's serialisation function to capture the required shaders.
	- Each target platform has its own list of cached shader formats, analogous to the list of targeted RHIs. Presently only the Mac implements this.
	- Code cached shaders are now compressed (for size) to reduce the overhead associated with keeping all the shader code around - this works esp. well for text-based formats like GLSL.

Change 2732365 on 2015/10/16 by Josh.Adams

	- Packaging a TVOS .ipa now works (still haven't tried any of the Editor integration like Launch On)

Change 2733170 on 2015/10/18 by Terence.Burns

	Fix for Android IAP query not returning entire inventory.

Change 2733174 on 2015/10/18 by Terence.Burns

	Fix Movie player issue where wait for movie to finish isnt being respected.

	Seems a stray bUserCanceled event flag was causing this not to be observed.

	Added some verbose logging to apple movie player.

Change 2733488 on 2015/10/19 by Mark.Satterthwaite

	Added the ability to merge the .ushadercache files used by the ShaderCache to store shader & draw state information.
	- Fixed a bug that would cause invalid shader membership and draw state information to be logged.
	- Added a separate command-line tool to merge shader cache files, currently Mac-only but in theory should work on other platforms too.

Change 2735226 on 2015/10/20 by Mark.Satterthwaite

	Fix temporal AA rendering on GL/Mac OS X - you can't rely on EyeAdaptation values unless SM5 is available so only perform that code on SM5 & we must correctly clamp saturate(NaN) to 0 as the current hlslcc won't do that for us (& is required by the HLSL spec). The latter used to be clamped in the AA_ALPHA && AA_VELOCITY_WEIGHTING code block that was removed recently.
	#jira UE-21214
	#jira UE-19913

Change 2736722 on 2015/10/21 by Daniel.Lamb

	Improved performance of cooking stats system.

Change 2737172 on 2015/10/21 by Daniel.Lamb

	Improved cooking stats performance for ddc stats.
2015-12-10 16:56:55 -05:00

1287 lines
41 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "NetworkFilePrivatePCH.h"
#include "NetworkPlatformFile.h"
#include "MultichannelTCP.h"
#include "DerivedDataCacheInterface.h"
#include "PackageName.h"
#include "HTTPTransport.h"
#include "TCPTransport.h"
#if WITH_UNREAL_DEVELOPER_TOOLS
#include "Developer/PackageDependencyInfo/Public/PackageDependencyInfo.h"
#endif //WITH_UNREAL_DEVELOPER_TOOLS
#include "IPlatformFileModule.h"
DEFINE_LOG_CATEGORY(LogNetworkPlatformFile);
FString FNetworkPlatformFile::MP4Extension = TEXT(".mp4");
FNetworkPlatformFile::FNetworkPlatformFile()
: bHasLoadedDDCDirectories(false)
, InnerPlatformFile(NULL)
, bIsUsable(false)
, FinishedAsyncNetworkReadUnsolicitedFiles(NULL)
, FinishedAsyncWriteUnsolicitedFiles(NULL)
, Transport(NULL)
{
}
bool FNetworkPlatformFile::ShouldBeUsed(IPlatformFile* Inner, const TCHAR* CmdLine) const
{
FString HostIp;
return FParse::Value(CmdLine, TEXT("-FileHostIP="), HostIp);
}
ITransport *CreateTransportForHostAddress(const FString &HostIp )
{
if ( HostIp.StartsWith(TEXT("tcp://")))
{
return new FTCPTransport();
}
if ( HostIp.StartsWith(TEXT("http://")))
{
#if ENABLE_HTTP_FOR_NFS
return new FHTTPTransport();
#endif
}
// no transport specified assuming tcp
return new FTCPTransport();
}
bool FNetworkPlatformFile::Initialize(IPlatformFile* Inner, const TCHAR* CmdLine)
{
bool bResult = false;
FString HostIpString;
if (FParse::Value(CmdLine, TEXT("-FileHostIP="), HostIpString))
{
TArray<FString> HostIpList;
if (HostIpString.ParseIntoArray(HostIpList, TEXT("+"), true) > 0)
{
for (int32 HostIpIndex = 0; !bResult && HostIpIndex < HostIpList.Num(); ++HostIpIndex)
{
// Try to initialize with each of the IP addresses found in the command line until we
// get a working one.
// find the correct transport for this ip address
Transport = CreateTransportForHostAddress( HostIpList[HostIpIndex] );
UE_LOG(LogNetworkPlatformFile, Warning, TEXT("Created transport for %s."), *HostIpList[HostIpIndex]);
if ( Transport )
{
bResult = Transport->Initialize( *HostIpList[HostIpIndex] ) && InitializeInternal(Inner, *HostIpList[HostIpIndex]);
if (bResult)
break;
UE_LOG(LogNetworkPlatformFile, Warning, TEXT("Failed to initialize %s."), *HostIpList[HostIpIndex]);
// try a different host might be a different protocol
delete Transport;
}
Transport = NULL;
}
}
}
return bResult;
}
bool FNetworkPlatformFile::InitializeInternal(IPlatformFile* Inner, const TCHAR* HostIP)
{
// This platform file requires an inner.
check(Inner != NULL);
InnerPlatformFile = Inner;
if (HostIP == NULL)
{
UE_LOG(LogNetworkPlatformFile, Error, TEXT("No Host IP specified in the commandline."));
bIsUsable = false;
return false;
}
// Save and Intermediate directories are always local
LocalDirectories.Add(FPaths::EngineDir() / TEXT("Binaries"));
LocalDirectories.Add(FPaths::EngineIntermediateDir());
LocalDirectories.Add(FPaths::GameDir() / TEXT("Binaries"));
LocalDirectories.Add(FPaths::GameIntermediateDir());
LocalDirectories.Add(FPaths::GameSavedDir() / TEXT("Backup"));
LocalDirectories.Add(FPaths::GameSavedDir() / TEXT("Config"));
LocalDirectories.Add(FPaths::GameSavedDir() / TEXT("Logs"));
LocalDirectories.Add(FPaths::GameSavedDir() / TEXT("Sandboxes"));
InnerPlatformFile->GetLowerLevel()->AddLocalDirectories(LocalDirectories);
FNetworkFileArchive Payload(NFS_Messages::Heartbeat);
FArrayReader Out;
if (!SendPayloadAndReceiveResponse(Payload,Out))
bIsUsable = true;
// lets see we can test whether the server is up.
if (Out.Num())
{
FCommandLine::AddToSubprocessCommandline( *FString::Printf( TEXT("-FileHostIP=%s"), HostIP ) );
bIsUsable = true;
}
return bIsUsable;
}
bool FNetworkPlatformFile::SendPayloadAndReceiveResponse(TArray<uint8>& In, TArray<uint8>& Out)
{
if ( FinishedAsyncNetworkReadUnsolicitedFiles )
{
delete FinishedAsyncNetworkReadUnsolicitedFiles;
FinishedAsyncNetworkReadUnsolicitedFiles = NULL;
}
return Transport->SendPayloadAndReceiveResponse( In, Out );
}
bool FNetworkPlatformFile::ReceiveResponse(TArray<uint8> &Out )
{
return Transport->ReceiveResponse( Out );
}
void FNetworkPlatformFile::InitializeAfterSetActive()
{
double NetworkFileStartupTime = 0.0;
{
SCOPE_SECONDS_COUNTER(NetworkFileStartupTime);
// send the filenames and timestamps to the server
FNetworkFileArchive Payload(NFS_Messages::GetFileList);
FillGetFileList(Payload, false);
// send the directories over, and wait for a response
FArrayReader Response;
if (!SendPayloadAndReceiveResponse(Payload, Response))
{
delete Transport;
return;
}
else
{
// receive the cooked version information
int32 ServerPackageVersion = 0;
int32 ServerPackageLicenseeVersion = 0;
ProcessServerInitialResponse(Response, ServerPackageVersion, ServerPackageLicenseeVersion);
/* The server root content directories */
TArray<FString> ServerRootContentDirectories;
Response << ServerRootContentDirectories;
// receive a list of the cache files and their timestamps
TMap<FString, FDateTime> ServerCachedFiles;
Response << ServerCachedFiles;
bool bDeleteAllFiles = true;
// Check the stored cooked version
FString CookedVersionFile = FPaths::GeneratedConfigDir() / TEXT("CookedVersion.txt");
if (InnerPlatformFile->FileExists(*CookedVersionFile) == true)
{
IFileHandle* FileHandle = InnerPlatformFile->OpenRead(*CookedVersionFile);
if (FileHandle != NULL)
{
int32 StoredPackageCookedVersion;
int32 StoredPackageCookedLicenseeVersion;
if (FileHandle->Read((uint8*)&StoredPackageCookedVersion, sizeof(int32)) == true)
{
if (FileHandle->Read((uint8*)&StoredPackageCookedLicenseeVersion, sizeof(int32)) == true)
{
if ((ServerPackageVersion == StoredPackageCookedVersion) &&
(ServerPackageLicenseeVersion == StoredPackageCookedLicenseeVersion))
{
bDeleteAllFiles = false;
}
else
{
UE_LOG(LogNetworkPlatformFile, Display,
TEXT("Engine version mismatch: Server %d.%d, Stored %d.%d\n"),
ServerPackageVersion, ServerPackageLicenseeVersion,
StoredPackageCookedVersion, StoredPackageCookedLicenseeVersion);
}
}
}
delete FileHandle;
}
}
else
{
UE_LOG(LogNetworkPlatformFile, Display, TEXT("Cooked version file missing: %s\n"), *CookedVersionFile);
}
if (bDeleteAllFiles == true)
{
// Make sure the config file exists...
InnerPlatformFile->CreateDirectoryTree(*(FPaths::GeneratedConfigDir()));
// Update the cooked version file
IFileHandle* FileHandle = InnerPlatformFile->OpenWrite(*CookedVersionFile);
if (FileHandle != NULL)
{
FileHandle->Write((const uint8*)&ServerPackageVersion, sizeof(int32));
FileHandle->Write((const uint8*)&ServerPackageLicenseeVersion, sizeof(int32));
delete FileHandle;
}
}
// list of directories to skip
TArray<FString> DirectoriesToSkip;
TArray<FString> DirectoriesToNotRecurse;
// use the timestamp grabbing visitor to get all the content times
FLocalTimestampDirectoryVisitor Visitor(*InnerPlatformFile, DirectoriesToSkip, DirectoriesToNotRecurse, false);
/*TArray<FString> RootContentPaths;
FPackageName::QueryRootContentPaths(RootContentPaths); */
for (TArray<FString>::TConstIterator RootPathIt(ServerRootContentDirectories); RootPathIt; ++RootPathIt)
{
/*const FString& RootPath = *RootPathIt;
const FString& ContentFolder = FPackageName::LongPackageNameToFilename(RootPath);*/
const FString& ContentFolder = *RootPathIt;
InnerPlatformFile->IterateDirectory( *ContentFolder, Visitor);
}
// delete out of date files using the server cached files
for (TMap<FString, FDateTime>::TIterator It(ServerCachedFiles); It; ++It)
{
bool bDeleteFile = bDeleteAllFiles;
FString ServerFile = It.Key();
// Convert the filename to the client version
ConvertServerFilenameToClientFilename(ServerFile);
// Set it in the visitor file times list
// If there is any pathing difference (relative path, or whatever) between the server's filelist and the results
// of platform directory iteration then this will Add a new entry rather than override the existing one. This causes local file deletes
// and longer loads as we will never see the benefits of local device caching.
Visitor.FileTimes.Add(ServerFile, FDateTime::MinValue());
if (bDeleteFile == false)
{
// Check the time stamps...
// get local time
FDateTime LocalTime = InnerPlatformFile->GetTimeStamp(*ServerFile);
// If local time == MinValue than the file does not exist in the cache.
if (LocalTime != FDateTime::MinValue())
{
FDateTime ServerTime = It.Value();
// delete if out of date
// We will use 1.0 second as the tolerance to cover any platform differences in resolution
FTimespan TimeDiff = LocalTime - ServerTime;
double TimeDiffInSeconds = TimeDiff.GetTotalSeconds();
bDeleteFile = (TimeDiffInSeconds > 1.0) || (TimeDiffInSeconds < -1.0);
if (bDeleteFile == true)
{
if (InnerPlatformFile->FileExists(*ServerFile) == true)
{
UE_LOG(LogNetworkPlatformFile, Display, TEXT("Deleting cached file: TimeDiff %5.3f, %s"), TimeDiffInSeconds, *It.Key());
}
else
{
// It's a directory
bDeleteFile = false;
}
}
else
{
UE_LOG(LogNetworkPlatformFile, Display, TEXT("Keeping cached file: %s, TimeDiff worked out ok"), *ServerFile);
}
}
}
if (bDeleteFile == true)
{
InnerPlatformFile->DeleteFile(*ServerFile);
}
}
// Any content files we have locally that were not cached, delete them
for (TMap<FString, FDateTime>::TIterator It(Visitor.FileTimes); It; ++It)
{
if (It.Value() != FDateTime::MinValue())
{
// This was *not* found in the server file list... delete it
UE_LOG(LogNetworkPlatformFile, Display, TEXT("Deleting cached file: %s"), *It.Key());
InnerPlatformFile->DeleteFile(*It.Key());
}
}
// make sure we can sync a file
FString TestSyncFile = FPaths::Combine(*(FPaths::EngineDir()), TEXT("Config/BaseEngine.ini"));
InnerPlatformFile->SetReadOnly(*TestSyncFile, false);
InnerPlatformFile->DeleteFile(*TestSyncFile);
if (InnerPlatformFile->FileExists(*TestSyncFile))
{
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Could not delete file sync test file %s."), *TestSyncFile);
}
EnsureFileIsLocal(TestSyncFile);
if (!InnerPlatformFile->FileExists(*TestSyncFile) || InnerPlatformFile->FileSize(*TestSyncFile) < 1)
{
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Could not sync test file %s."), *TestSyncFile);
}
}
}
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Network file startup time: %5.3f seconds\n"), NetworkFileStartupTime);
}
FNetworkPlatformFile::~FNetworkPlatformFile()
{
if (!GIsRequestingExit) // the socket subsystem is probably already gone, so it will crash if we clean up
{
if ( FinishedAsyncNetworkReadUnsolicitedFiles )
{
delete FinishedAsyncNetworkReadUnsolicitedFiles; // wait here for any async unsolicited files to finish reading being read from the network
FinishedAsyncNetworkReadUnsolicitedFiles = NULL;
}
if ( FinishedAsyncWriteUnsolicitedFiles )
{
delete FinishedAsyncWriteUnsolicitedFiles; // wait here for any async unsolicited files to finish writing
FinishedAsyncWriteUnsolicitedFiles = NULL;
}
delete Transport; // close our sockets.
}
}
bool FNetworkPlatformFile::DeleteFile(const TCHAR* Filename)
{
// FScopeLock ScopeLock(&SynchronizationObject);
// make and send payload (this is how we would do for sending all commands over the network)
// FNetworkFileArchive Payload(NFS_Messages::DeleteFile);
// Payload << Filename;
// return FNFSMessageHeader::WrapAndSendPayload(Payload, FileSocket);
// perform a local operation
return InnerPlatformFile->DeleteFile(Filename);
}
bool FNetworkPlatformFile::MoveFile(const TCHAR* To, const TCHAR* From)
{
// FScopeLock ScopeLock(&SynchronizationObject);
// make and send payload (this is how we would do for sending all commands over the network)
// FNetworkFileArchive Payload(NFS_Messages::MoveFile);
// Payload << To << From;
// return FNFSMessageHeader::WrapAndSendPayload(Payload, FileSocket);
FString RelativeFrom = From;
MakeStandardNetworkFilename(RelativeFrom);
// don't copy files in local directories
if (!IsInLocalDirectory(RelativeFrom))
{
// make sure the source file exists here
EnsureFileIsLocal(RelativeFrom);
}
// perform a local operation
return InnerPlatformFile->MoveFile(To, From);
}
bool FNetworkPlatformFile::SetReadOnly(const TCHAR* Filename, bool bNewReadOnlyValue)
{
// FScopeLock ScopeLock(&SynchronizationObject);
// perform a local operation
return InnerPlatformFile->SetReadOnly(Filename, bNewReadOnlyValue);
}
void FNetworkPlatformFile::SetTimeStamp(const TCHAR* Filename, FDateTime DateTime)
{
// perform a local operation
InnerPlatformFile->SetTimeStamp(Filename, DateTime);
}
IFileHandle* FNetworkPlatformFile::OpenRead(const TCHAR* Filename, bool bAllowWrite)
{
// FScopeLock ScopeLock(&SynchronizationObject);
FString RelativeFilename = Filename;
MakeStandardNetworkFilename(RelativeFilename);
// don't copy files in local directories
if (!IsInLocalDirectory(RelativeFilename))
{
EnsureFileIsLocal(RelativeFilename);
}
double StartTime;
float ThisTime;
StartTime = FPlatformTime::Seconds();
IFileHandle* Result = InnerPlatformFile->OpenRead(Filename, bAllowWrite);
ThisTime = 1000.0f * float(FPlatformTime::Seconds() - StartTime);
//UE_LOG(LogNetworkPlatformFile, Display, TEXT("Open local file %6.2fms"), ThisTime);
return Result;
}
IFileHandle* FNetworkPlatformFile::OpenWrite(const TCHAR* Filename, bool bAppend, bool bAllowRead)
{
// FScopeLock ScopeLock(&SynchronizationObject);
// just let the physical file interface write the file (we don't write over the network)
return InnerPlatformFile->OpenWrite(Filename, bAppend, bAllowRead);
}
bool FNetworkPlatformFile::CreateDirectoryTree(const TCHAR* Directory)
{
// FScopeLock ScopeLock(&SynchronizationObject);
// perform a local operation
return InnerPlatformFile->CreateDirectoryTree(Directory);
}
bool FNetworkPlatformFile::CreateDirectory(const TCHAR* Directory)
{
// FScopeLock ScopeLock(&SynchronizationObject);
// perform a local operation
return InnerPlatformFile->CreateDirectory(Directory);
}
bool FNetworkPlatformFile::DeleteDirectory(const TCHAR* Directory)
{
// FScopeLock ScopeLock(&SynchronizationObject);
// perform a local operation
return InnerPlatformFile->DeleteDirectory(Directory);
}
FFileStatData FNetworkPlatformFile::GetStatData(const TCHAR* FilenameOrDirectory)
{
// FScopeLock ScopeLock(&SynchronizationObject);
// perform a local operation
return InnerPlatformFile->GetStatData(FilenameOrDirectory);
}
bool FNetworkPlatformFile::IterateDirectory(const TCHAR* InDirectory, IPlatformFile::FDirectoryVisitor& Visitor)
{
// FScopeLock ScopeLock(&SynchronizationObject);
// for .dll, etc searches that don't specify a path, we need to strip off the path
// before we send it to the visitor
bool bHadNoPath = InDirectory[0] == 0;
// local files go right to the source
FString RelativeDirectory = InDirectory;
MakeStandardNetworkFilename(RelativeDirectory);
if (IsInLocalDirectory(RelativeDirectory))
{
return InnerPlatformFile->IterateDirectory(InDirectory, Visitor);
}
// we loop until this is false
bool RetVal = true;
FServerTOC::FDirectory* ServerDirectory = ServerFiles.FindDirectory(RelativeDirectory);
if (ServerDirectory != NULL)
{
// loop over the server files and look if they are in this exact directory
for (FServerTOC::FDirectory::TIterator It(*ServerDirectory); It && RetVal == true; ++It)
{
if (FPaths::GetPath(It.Key()) == RelativeDirectory)
{
// timestamps of 0 mean directories
bool bIsDirectory = It.Value() == 0;
// visit (stripping off the path if needed)
RetVal = Visitor.Visit(bHadNoPath ? *FPaths::GetCleanFilename(It.Key()) : *It.Key(), bIsDirectory);
}
}
}
return RetVal;
}
bool FNetworkPlatformFile::IterateDirectoryRecursively(const TCHAR* InDirectory, IPlatformFile::FDirectoryVisitor& Visitor)
{
// FScopeLock ScopeLock(&SynchronizationObject);
// local files go right to the source
FString RelativeDirectory = InDirectory;
MakeStandardNetworkFilename(RelativeDirectory);
if (IsInLocalDirectory(RelativeDirectory))
{
return InnerPlatformFile->IterateDirectoryRecursively(InDirectory, Visitor);
}
// we loop until this is false
bool RetVal = true;
for (TMap<FString, FServerTOC::FDirectory*>::TIterator DirIt(ServerFiles.Directories); DirIt && RetVal == true; ++DirIt)
{
if (DirIt.Key().StartsWith(RelativeDirectory))
{
FServerTOC::FDirectory& ServerDirectory = *DirIt.Value();
// loop over the server files and look if they are in this exact directory
for (FServerTOC::FDirectory::TIterator It(ServerDirectory); It && RetVal == true; ++It)
{
// timestamps of 0 mean directories
bool bIsDirectory = It.Value() == 0;
// visit!
RetVal = Visitor.Visit(*It.Key(), bIsDirectory);
}
}
}
return RetVal;
}
bool FNetworkPlatformFile::IterateDirectoryStat(const TCHAR* InDirectory, IPlatformFile::FDirectoryStatVisitor& Visitor)
{
// FScopeLock ScopeLock(&SynchronizationObject);
// for .dll, etc searches that don't specify a path, we need to strip off the path
// before we send it to the visitor
bool bHadNoPath = InDirectory[0] == 0;
// local files go right to the source
FString RelativeDirectory = InDirectory;
MakeStandardNetworkFilename(RelativeDirectory);
if (IsInLocalDirectory(RelativeDirectory))
{
return InnerPlatformFile->IterateDirectoryStat(InDirectory, Visitor);
}
// we loop until this is false
bool RetVal = true;
FServerTOC::FDirectory* ServerDirectory = ServerFiles.FindDirectory(RelativeDirectory);
if (ServerDirectory != NULL)
{
// loop over the server files and look if they are in this exact directory
for (FServerTOC::FDirectory::TIterator It(*ServerDirectory); It && RetVal == true; ++It)
{
if (FPaths::GetPath(It.Key()) == RelativeDirectory)
{
// timestamps of 0 mean directories
bool bIsDirectory = It.Value() == 0;
// todo: this data is just wrong for most things, but can we afford to get the files from the server to get the correct info? Could the server provide this instead?
const FFileStatData StatData(
FDateTime::MinValue(),
FDateTime::MinValue(),
(bIsDirectory) ? FDateTime::MinValue() : It.Value(),
-1, // FileSize
bIsDirectory,
true // IsReadOnly
);
// visit (stripping off the path if needed)
RetVal = Visitor.Visit(bHadNoPath ? *FPaths::GetCleanFilename(It.Key()) : *It.Key(), StatData);
}
}
}
return RetVal;
}
bool FNetworkPlatformFile::IterateDirectoryStatRecursively(const TCHAR* InDirectory, IPlatformFile::FDirectoryStatVisitor& Visitor)
{
// FScopeLock ScopeLock(&SynchronizationObject);
// local files go right to the source
FString RelativeDirectory = InDirectory;
MakeStandardNetworkFilename(RelativeDirectory);
if (IsInLocalDirectory(RelativeDirectory))
{
return InnerPlatformFile->IterateDirectoryStatRecursively(InDirectory, Visitor);
}
// we loop until this is false
bool RetVal = true;
for (TMap<FString, FServerTOC::FDirectory*>::TIterator DirIt(ServerFiles.Directories); DirIt && RetVal == true; ++DirIt)
{
if (DirIt.Key().StartsWith(RelativeDirectory))
{
FServerTOC::FDirectory& ServerDirectory = *DirIt.Value();
// loop over the server files and look if they are in this exact directory
for (FServerTOC::FDirectory::TIterator It(ServerDirectory); It && RetVal == true; ++It)
{
// timestamps of 0 mean directories
bool bIsDirectory = It.Value() == 0;
// todo: this data is just wrong for most things, but can we afford to get the files from the server to get the correct info? Could the server provide this instead?
const FFileStatData StatData(
FDateTime::MinValue(),
FDateTime::MinValue(),
(bIsDirectory) ? FDateTime::MinValue() : It.Value(),
0, // FileSize
bIsDirectory,
true // IsReadOnly
);
// visit!
RetVal = Visitor.Visit(*It.Key(), StatData);
}
}
}
return RetVal;
}
bool FNetworkPlatformFile::DeleteDirectoryRecursively(const TCHAR* Directory)
{
// FScopeLock ScopeLock(&SynchronizationObject);
// perform a local operation
return InnerPlatformFile->DeleteDirectory(Directory);
}
bool FNetworkPlatformFile::CopyFile(const TCHAR* To, const TCHAR* From)
{
// FScopeLock ScopeLock(&SynchronizationObject);
FString RelativeFrom = From;
MakeStandardNetworkFilename(RelativeFrom);
// don't copy files in local directories
if (!IsInLocalDirectory(RelativeFrom))
{
// make sure the source file exists here
EnsureFileIsLocal(RelativeFrom);
}
// perform a local operation
return InnerPlatformFile->CopyFile(To, From);
}
FString FNetworkPlatformFile::ConvertToAbsolutePathForExternalAppForRead( const TCHAR* Filename )
{
FString RelativeFrom = Filename;
MakeStandardNetworkFilename(RelativeFrom);
if (!IsInLocalDirectory(RelativeFrom))
{
EnsureFileIsLocal(RelativeFrom);
}
return InnerPlatformFile->ConvertToAbsolutePathForExternalAppForRead(Filename);
}
FString FNetworkPlatformFile::ConvertToAbsolutePathForExternalAppForWrite( const TCHAR* Filename )
{
FString RelativeFrom = Filename;
MakeStandardNetworkFilename(RelativeFrom);
if (!IsInLocalDirectory(RelativeFrom))
{
EnsureFileIsLocal(RelativeFrom);
}
return InnerPlatformFile->ConvertToAbsolutePathForExternalAppForWrite(Filename);
}
bool FNetworkPlatformFile::DirectoryExists(const TCHAR* Directory)
{
if (InnerPlatformFile->DirectoryExists(Directory))
{
return true;
}
// If there are any syncable files in this directory, consider it existing
FString RelativeDirectory = Directory;
MakeStandardNetworkFilename(RelativeDirectory);
FServerTOC::FDirectory* ServerDirectory = ServerFiles.FindDirectory(RelativeDirectory);
return ServerDirectory != NULL;
}
void FNetworkPlatformFile::GetFileInfo(const TCHAR* Filename, FFileInfo& Info)
{
FString RelativeFilename = Filename;
MakeStandardNetworkFilename(RelativeFilename);
// don't copy files in local directories
if (!IsInLocalDirectory(RelativeFilename))
{
EnsureFileIsLocal(RelativeFilename);
}
const FFileStatData StatData = InnerPlatformFile->GetStatData(Filename);
Info.FileExists = StatData.bIsValid && !StatData.bIsDirectory;
Info.ReadOnly = StatData.bIsReadOnly;
Info.Size = StatData.FileSize;
Info.TimeStamp = StatData.ModificationTime;
Info.AccessTimeStamp = StatData.AccessTime;
}
void FNetworkPlatformFile::ConvertServerFilenameToClientFilename(FString& FilenameToConvert)
{
FNetworkPlatformFile::ConvertServerFilenameToClientFilename(FilenameToConvert, ServerEngineDir, ServerGameDir);
}
void FNetworkPlatformFile::FillGetFileList(FNetworkFileArchive& Payload, bool bInStreamingFileRequest)
{
TArray<FString> TargetPlatformNames;
FPlatformMisc::GetValidTargetPlatforms(TargetPlatformNames);
FString GameName = FApp::GetGameName();
if (FPaths::IsProjectFilePathSet())
{
GameName = FPaths::GetProjectFilePath();
}
FString EngineRelPath = FPaths::EngineDir();
FString EngineRelPluginPath = FPaths::EnginePluginsDir();
FString GameRelPath = FPaths::GameDir();
FString GameRelPluginPath = FPaths::GamePluginsDir();
TArray<FString> Directories;
Directories.Add(EngineRelPath);
Directories.Add(EngineRelPluginPath);
Directories.Add(GameRelPath);
Directories.Add(GameRelPluginPath);
Payload << TargetPlatformNames;
Payload << GameName;
Payload << EngineRelPath;
Payload << GameRelPath;
Payload << Directories;
Payload << bInStreamingFileRequest;
}
void FNetworkPlatformFile::ProcessServerInitialResponse(FArrayReader& InResponse, int32 OutServerPackageVersion, int32 OutServerPackageLicenseeVersion)
{
// Receive the cooked version information.
InResponse << OutServerPackageVersion;
InResponse << OutServerPackageLicenseeVersion;
// receive the server engine and game dir
InResponse << ServerEngineDir;
InResponse << ServerGameDir;
UE_LOG(LogNetworkPlatformFile, Display, TEXT(" Server EngineDir = %s"), *ServerEngineDir);
UE_LOG(LogNetworkPlatformFile, Display, TEXT(" Local EngineDir = %s"), *FPaths::EngineDir());
UE_LOG(LogNetworkPlatformFile, Display, TEXT(" Server GameDir = %s"), *ServerGameDir);
UE_LOG(LogNetworkPlatformFile, Display, TEXT(" Local GameDir = %s"), *FPaths::GameDir());
// Receive a list of files and their timestamps.
TMap<FString, FDateTime> ServerFileMap;
InResponse << ServerFileMap;
for (TMap<FString, FDateTime>::TIterator It(ServerFileMap); It; ++It)
{
FString ServerFile = It.Key();
ConvertServerFilenameToClientFilename(ServerFile);
ServerFiles.AddFileOrDirectory(ServerFile, It.Value());
}
}
bool FNetworkPlatformFile::SendReadMessage(uint8* Destination, int64 BytesToRead)
{
// FScopeLock ScopeLock(&SynchronizationObject);
return true;
}
bool FNetworkPlatformFile::SendWriteMessage(const uint8* Source, int64 BytesToWrite)
{
// FScopeLock ScopeLock(&SynchronizationObject);
return true;
}
bool FNetworkPlatformFile::SendMessageToServer(const TCHAR* Message, IPlatformFile::IFileServerMessageHandler* Handler)
{
// handle the recompile shaders message
// @todo: Maybe we should just send the string message to the server, but then we'd have to
// handle the return from the server in a generic way
if (FCString::Stricmp(Message, TEXT("RecompileShaders")) == 0)
{
FNetworkFileArchive Payload(NFS_Messages::RecompileShaders);
// let the handler fill out the object
Handler->FillPayload(Payload);
FArrayReader Response;
if (!SendPayloadAndReceiveResponse(Payload, Response))
{
return false;
}
// locally delete any files that were modified on the server, so that any read will recache the file
// this has to be done in this class, not in the Handler (which can't access these members)
TArray<FString> ModifiedFiles;
Response << ModifiedFiles;
if( InnerPlatformFile != NULL )
{
for (int32 Index = 0; Index < ModifiedFiles.Num(); Index++)
{
InnerPlatformFile->DeleteFile(*ModifiedFiles[Index]);
CachedLocalFiles.Remove(ModifiedFiles[Index]);
ServerFiles.AddFileOrDirectory(ModifiedFiles[Index], FDateTime::UtcNow());
}
}
// let the handler process the response directly
Handler->ProcessResponse(Response);
}
return true;
}
static FThreadSafeCounter OutstandingAsyncWrites;
class FAsyncNetworkWriteWorker : public FNonAbandonableTask
{
public:
/** Filename To write to**/
FString Filename;
/** An archive to read the file contents from */
FArchive* FileArchive;
/** timestamp for the file **/
FDateTime ServerTimeStamp;
IPlatformFile& InnerPlatformFile;
FScopedEvent* Event;
uint8 Buffer[128 * 1024];
/** Constructor
*/
FAsyncNetworkWriteWorker(const TCHAR* InFilename, FArchive* InArchive, FDateTime InServerTimeStamp, IPlatformFile* InInnerPlatformFile, FScopedEvent* InEvent)
: Filename(InFilename)
, FileArchive(InArchive)
, ServerTimeStamp(InServerTimeStamp)
, InnerPlatformFile(*InInnerPlatformFile)
, Event(InEvent)
{
}
/** Write the file */
void DoWork()
{
if (InnerPlatformFile.FileExists(*Filename))
{
InnerPlatformFile.SetReadOnly(*Filename, false);
InnerPlatformFile.DeleteFile(*Filename);
}
// Read FileSize first so that the correct amount of data is read from the archive
// before exiting this worker.
uint64 FileSize;
*FileArchive << FileSize;
if (ServerTimeStamp != FDateTime::MinValue()) // if the file didn't actually exist on the server, don't create a zero byte file
{
FString TempFilename = Filename + TEXT(".tmp");
InnerPlatformFile.CreateDirectoryTree(*FPaths::GetPath(Filename));
{
TAutoPtr<IFileHandle> FileHandle;
FileHandle = InnerPlatformFile.OpenWrite(*TempFilename);
if (!FileHandle)
{
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Could not open file for writing '%s'."), *TempFilename);
}
// now write the file from bytes pulled from the archive
// read/write a chunk at a time
uint64 RemainingData = FileSize;
while (RemainingData)
{
// read next chunk from archive
uint32 LocalSize = FPlatformMath::Min<uint32>(ARRAY_COUNT(Buffer), RemainingData);
FileArchive->Serialize(Buffer, LocalSize);
// write it out
if (!FileHandle->Write(Buffer, LocalSize))
{
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Could not write '%s'."), *TempFilename);
}
// decrement how much is left
RemainingData -= LocalSize;
}
// delete async write archives
if (Event)
{
delete FileArchive;
}
if (InnerPlatformFile.FileSize(*TempFilename) != FileSize)
{
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Did not write '%s'."), *TempFilename);
}
}
// rename from temp filename to real filename
InnerPlatformFile.MoveFile(*Filename, *TempFilename);
// now set the server's timestamp on the local file (so we can make valid comparisons)
InnerPlatformFile.SetTimeStamp(*Filename, ServerTimeStamp);
FDateTime CheckTime = InnerPlatformFile.GetTimeStamp(*Filename);
if (CheckTime < ServerTimeStamp)
{
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Could Not Set Timestamp '%s'."), *Filename);
}
}
if (Event)
{
if (OutstandingAsyncWrites.Decrement() == 0)
{
Event->Trigger(); // last file, fire trigger
}
}
}
FORCEINLINE TStatId GetStatId() const
{
return TStatId();
//RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncNetworkWriteWorker, STATGROUP_ThreadPoolAsyncTasks);
}
};
/**
* Write a file async or sync, with the data coming from a TArray or an FArchive/Filesize
*/
void SyncWriteFile(FArchive* Archive, const FString& Filename, FDateTime ServerTimeStamp, IPlatformFile& InnerPlatformFile)
{
FScopedEvent* NullEvent = NULL;
(new FAutoDeleteAsyncTask<FAsyncNetworkWriteWorker>(*Filename, Archive, ServerTimeStamp, &InnerPlatformFile, NullEvent))->StartSynchronousTask();
}
void AsyncWriteFile(FArchive* Archive, const FString& Filename, FDateTime ServerTimeStamp, IPlatformFile& InnerPlatformFile, FScopedEvent* Event = NULL)
{
(new FAutoDeleteAsyncTask<FAsyncNetworkWriteWorker>(*Filename, Archive, ServerTimeStamp, &InnerPlatformFile, Event))->StartBackgroundTask();
}
void AsyncReadUnsolicitedFiles(int32 InNumUnsolictedFiles, FNetworkPlatformFile& InNetworkFile, IPlatformFile& InInnerPlatformFile, FString& InServerEngineDir, FString& InServerGameDir, FScopedEvent *InNetworkDoneEvent, FScopedEvent *InWritingDoneEvent)
{
class FAsyncReadUnsolicitedFile : public FNonAbandonableTask
{
public:
int32 NumUnsolictedFiles;
FNetworkPlatformFile& NetworkFile;
IPlatformFile& InnerPlatformFile;
FString ServerEngineDir;
FString ServerGameDir;
FScopedEvent* NetworkDoneEvent; // finished using the network
FScopedEvent* WritingDoneEvent; // finished writing the files to disk
FAsyncReadUnsolicitedFile(int32 In_NumUnsolictedFiles, FNetworkPlatformFile* In_NetworkFile, IPlatformFile* In_InnerPlatformFile, FString& In_ServerEngineDir, FString& In_ServerGameDir, FScopedEvent *In_NetworkDoneEvent, FScopedEvent *In_WritingDoneEvent )
: NumUnsolictedFiles(In_NumUnsolictedFiles)
, NetworkFile(*In_NetworkFile)
, InnerPlatformFile(*In_InnerPlatformFile)
, ServerEngineDir(In_ServerEngineDir)
, ServerGameDir(In_ServerGameDir)
, NetworkDoneEvent(In_NetworkDoneEvent)
, WritingDoneEvent(In_WritingDoneEvent)
{
}
/** Write the file */
void DoWork()
{
OutstandingAsyncWrites.Add( NumUnsolictedFiles );
for (int32 Index = 0; Index < NumUnsolictedFiles; Index++)
{
FArrayReader* UnsolictedResponse = new FArrayReader;
if (!NetworkFile.ReceiveResponse(*UnsolictedResponse))
{
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Receive failure!"));
return;
}
FString UnsolictedReplyFile;
*UnsolictedResponse << UnsolictedReplyFile;
if (!UnsolictedReplyFile.IsEmpty())
{
FNetworkPlatformFile::ConvertServerFilenameToClientFilename(UnsolictedReplyFile, ServerEngineDir, ServerGameDir);
// get the server file timestamp
FDateTime UnsolictedServerTimeStamp;
*UnsolictedResponse << UnsolictedServerTimeStamp;
// write the file by pulling out of the FArrayReader
AsyncWriteFile(UnsolictedResponse, UnsolictedReplyFile, UnsolictedServerTimeStamp, InnerPlatformFile, WritingDoneEvent);
}
}
NetworkDoneEvent->Trigger();
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncReadUnsolicitedFile, STATGROUP_ThreadPoolAsyncTasks);
}
};
(new FAutoDeleteAsyncTask<FAsyncReadUnsolicitedFile>(InNumUnsolictedFiles, &InNetworkFile, &InInnerPlatformFile, InServerEngineDir, InServerGameDir, InNetworkDoneEvent, InWritingDoneEvent))->StartSynchronousTask();
}
bool FNetworkPlatformFile::IsMediaExtension(const TCHAR* Ext)
{
if (*Ext != TEXT('.'))
{
return MP4Extension.EndsWith(Ext);
}
else
{
return MP4Extension == Ext;
}
}
/**
* Given a filename, make sure the file exists on the local filesystem
*/
void FNetworkPlatformFile::EnsureFileIsLocal(const FString& Filename)
{
double StartTime;
float ThisTime;
StartTime = FPlatformTime::Seconds();
{
FScopeLock ScopeLock(&SynchronizationObject);
// have we already cached this file?
if (CachedLocalFiles.Find(Filename) != NULL)
{
return;
}
}
if ( FinishedAsyncNetworkReadUnsolicitedFiles )
{
delete FinishedAsyncNetworkReadUnsolicitedFiles; // wait here for any async unsolicited files to finish reading being read from the network
FinishedAsyncNetworkReadUnsolicitedFiles = NULL;
}
if( FinishedAsyncWriteUnsolicitedFiles)
{
delete FinishedAsyncWriteUnsolicitedFiles; // wait here for any async unsolicited files to finish writing to disk
FinishedAsyncWriteUnsolicitedFiles = NULL;
}
FScopeLock ScopeLock(&SynchronizationObject);
ThisTime = 1000.0f * float(FPlatformTime::Seconds() - StartTime);
//UE_LOG(LogNetworkPlatformFile, Display, TEXT("Lock and wait for old async writes %6.2fms"), ThisTime);
// have we already cached this file? (test again, since some other thread might have done this between waits)
if (CachedLocalFiles.Find(Filename) != NULL)
{
return;
}
// even if an error occurs later, we still want to remember not to try again
CachedLocalFiles.Add(Filename);
StartTime = FPlatformTime::Seconds();
// no need to read it if it already exists
// @todo: Handshake with server to delete files that are out of date
if (InnerPlatformFile->FileExists(*Filename))
{
return;
}
ThisTime = 1000.0f * float(FPlatformTime::Seconds() - StartTime);
//UE_LOG(LogNetworkPlatformFile, Display, TEXT("Check for local file %6.2fms - %s"), ThisTime, *Filename);
// this is a bit of a waste if we aren't doing cook on the fly, but we assume missing asset files are relatively rare
FString Extension = FPaths::GetExtension(Filename, true);
bool bIsCookable = GConfig && GConfig->IsReadyForUse() && (FPackageName::IsPackageExtension(*Extension) || IsMediaExtension(*Extension));
// we only copy files that actually exist on the server, can greatly reduce network traffic for, say,
// the INT file each package tries to load
if (!bIsCookable && ServerFiles.FindFile(Filename) == NULL)
{
// Uncomment this to have the server file list dumped
// the first time a file requested is not found.
#if 0
static bool sb_DumpedServer = false;
if (sb_DumpedServer == false)
{
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Dumping server files... %s not found\n"), *Filename);
for (TMap<FString, FServerTOC::FDirectory*>::TIterator ServerDumpIt(ServerFiles.Directories); ServerDumpIt; ++ServerDumpIt)
{
FServerTOC::FDirectory& Directory = *ServerDumpIt.Value();
for (FServerTOC::FDirectory::TIterator DirDumpIt(Directory); DirDumpIt; ++DirDumpIt)
{
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("%10s - %s\n"), *(DirDumpIt.Value().ToString()), *(DirDumpIt.Key()));
}
}
sb_DumpedServer = true;
}
#endif
return;
}
// send the filename over (cast away const here because we know this << will not modify the string)
FNetworkFileArchive Payload(NFS_Messages::SyncFile);
Payload << (FString&)Filename;
StartTime = FPlatformTime::Seconds();
// allocate array reader on the heap, because the SyncWriteFile function will delete it
FArrayReader Response;
if (!SendPayloadAndReceiveResponse(Payload, Response))
{
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Receive failure!"));
return;
}
ThisTime = 1000.0f * float(FPlatformTime::Seconds() - StartTime);
//UE_LOG(LogNetworkPlatformFile, Display, TEXT("Send and receive %6.2fms"), ThisTime);
StartTime = FPlatformTime::Seconds();
FString ReplyFile;
Response << ReplyFile;
ConvertServerFilenameToClientFilename(ReplyFile);
check(ReplyFile == Filename);
// get the server file timestamp
FDateTime ServerTimeStamp;
Response << ServerTimeStamp;
// write the file in chunks, synchronously
SyncWriteFile(&Response, ReplyFile, ServerTimeStamp, *InnerPlatformFile);
int32 NumUnsolictedFiles;
Response << NumUnsolictedFiles;
if (NumUnsolictedFiles)
{
check( FinishedAsyncNetworkReadUnsolicitedFiles == NULL );
check( FinishedAsyncWriteUnsolicitedFiles == NULL );
FinishedAsyncNetworkReadUnsolicitedFiles = new FScopedEvent;
FinishedAsyncWriteUnsolicitedFiles = new FScopedEvent;
AsyncReadUnsolicitedFiles(NumUnsolictedFiles, *this, *InnerPlatformFile, ServerEngineDir, ServerGameDir, FinishedAsyncNetworkReadUnsolicitedFiles, FinishedAsyncWriteUnsolicitedFiles);
}
ThisTime = 1000.0f * float(FPlatformTime::Seconds() - StartTime);
//UE_LOG(LogNetworkPlatformFile, Display, TEXT("Write file to local %6.2fms"), ThisTime);
}
static FString NetworkPlatformFileEndChop(TEXT("/"));
void FNetworkPlatformFile::MakeStandardNetworkFilename(FString& Filename)
{
FPaths::MakeStandardFilename(Filename);
Filename.RemoveFromEnd(NetworkPlatformFileEndChop, ESearchCase::CaseSensitive);
}
bool FNetworkPlatformFile::IsInLocalDirectoryUnGuarded(const FString& Filename)
{
// cache the directory of the input file
FString Directory = FPaths::GetPath(Filename);
// look if the file is in a local directory
for (int32 DirIndex = 0; DirIndex < LocalDirectories.Num(); DirIndex++)
{
if (Directory.StartsWith(LocalDirectories[DirIndex]))
{
return true;
}
}
// if not local, talk to the server
return false;
}
bool FNetworkPlatformFile::IsInLocalDirectory(const FString& Filename)
{
if (!bHasLoadedDDCDirectories)
{
// need to be careful here to avoid initializing the DDC from the wrong thread or using LocalDirectories while it is being initialized
FScopeLock ScopeLock(&LocalDirectoriesCriticalSection);
if (IsInGameThread() && GConfig && GConfig->IsReadyForUse())
{
// one time DDC directory initialization
// add any DDC directories to our list of local directories (local = inner platform file, it may
// actually live on a server, but it will use the platform's file system)
if (GetDerivedDataCache())
{
TArray<FString> DdcDirectories;
GetDerivedDataCacheRef().GetDirectories(DdcDirectories);
LocalDirectories.Append(DdcDirectories);
}
FPlatformMisc::MemoryBarrier();
bHasLoadedDDCDirectories = true;
}
return IsInLocalDirectoryUnGuarded(Filename);
}
// once the DDC is initialized, we don't need to lock a critical section anymore
return IsInLocalDirectoryUnGuarded(Filename);
}
void FNetworkPlatformFile::PerformHeartbeat()
{
// send the filename over (cast away const here because we know this << will not modify the string)
FNetworkFileArchive Payload(NFS_Messages::Heartbeat);
// send the filename over
FArrayReader Response;
if (!SendPayloadAndReceiveResponse(Payload, Response))
{
return;
}
// get any files that have been modified on the server -
TArray<FString> UpdatedFiles;
Response << UpdatedFiles;
// delete any outdated files from the client
// @todo: This may need a critical section around all calls to LowLevel in the other functions
// because we don't want to delete files while other threads are using them!
for (int32 FileIndex = 0; FileIndex < UpdatedFiles.Num(); FileIndex++)
{
UE_LOG(LogNetworkPlatformFile, Log, TEXT("Server updated file '%s', deleting local copy"), *UpdatedFiles[FileIndex]);
if (InnerPlatformFile->DeleteFile(*UpdatedFiles[FileIndex]) == false)
{
UE_LOG(LogNetworkPlatformFile, Error, TEXT("Failed to delete %s, someone is probably accessing without FNetworkPlatformFile, or we need better thread protection"), *UpdatedFiles[FileIndex]);
}
}
}
void FNetworkPlatformFile::ConvertServerFilenameToClientFilename(FString& FilenameToConvert, const FString& InServerEngineDir, const FString& InServerGameDir)
{
if (FilenameToConvert.StartsWith(InServerEngineDir))
{
FilenameToConvert = FilenameToConvert.Replace(*InServerEngineDir, *(FPaths::EngineDir()));
}
else if (FilenameToConvert.StartsWith(InServerGameDir))
{
FilenameToConvert = FilenameToConvert.Replace(*InServerGameDir, *(FPaths::GameDir()));
}
}
/**
* Module for the network file
*/
class FNetworkFileModule : public IPlatformFileModule
{
public:
virtual IPlatformFile* GetPlatformFile() override
{
static TScopedPointer<IPlatformFile> AutoDestroySingleton(new FNetworkPlatformFile());
return AutoDestroySingleton.GetOwnedPointer();
}
};
IMPLEMENT_MODULE(FNetworkFileModule, NetworkFile);