You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
1134 lines
37 KiB
C++
1134 lines
37 KiB
C++
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "NetworkFilePrivatePCH.h"
|
|
#include "NetworkPlatformFile.h"
|
|
#include "Sockets.h"
|
|
#include "MultichannelTCP.h"
|
|
#include "DerivedDataCacheInterface.h"
|
|
#include "PackageName.h"
|
|
|
|
#if WITH_UNREAL_DEVELOPER_TOOLS
|
|
#include "Developer/PackageDependencyInfo/Public/PackageDependencyInfo.h"
|
|
#endif //WITH_UNREAL_DEVELOPER_TOOLS
|
|
|
|
#include "IPlatformFileModule.h"
|
|
|
|
|
|
DEFINE_LOG_CATEGORY(LogNetworkPlatformFile);
|
|
|
|
|
|
FNetworkPlatformFile::FNetworkPlatformFile()
|
|
: bHasLoadedDDCDirectories(false)
|
|
, InnerPlatformFile(NULL)
|
|
, bIsUsable(false)
|
|
, FileServerPort(DEFAULT_FILE_SERVING_PORT)
|
|
, FileSocket(NULL)
|
|
, MCSocket(NULL)
|
|
, FinishedAsyncReadUnsolicitedFiles(NULL)
|
|
, FinishedAsyncWriteUnsolicitedFiles(NULL)
|
|
{
|
|
}
|
|
|
|
bool FNetworkPlatformFile::ShouldBeUsed(IPlatformFile* Inner, const TCHAR* CmdLine) const
|
|
{
|
|
FString HostIp;
|
|
return FParse::Value(CmdLine, TEXT("-FileHostIP="), HostIp);
|
|
}
|
|
|
|
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)
|
|
{
|
|
// Try to initialize with each of the IP addresses found in the command line until we
|
|
// get a working one.
|
|
for (int32 HostIpIndex = 0; !bResult && HostIpIndex < HostIpList.Num(); ++HostIpIndex)
|
|
{
|
|
bResult = InitializeInternal(Inner, *HostIpList[HostIpIndex]);
|
|
}
|
|
}
|
|
}
|
|
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"));
|
|
|
|
ISocketSubsystem* SSS = ISocketSubsystem::Get();
|
|
|
|
// convert the string to a ip addr structure
|
|
TSharedRef<FInternetAddr> Addr = SSS->CreateInternetAddr(0, FileServerPort);
|
|
bool bIsValid;
|
|
|
|
Addr->SetIp(HostIP, bIsValid);
|
|
|
|
if (bIsValid)
|
|
{
|
|
// create the socket
|
|
FileSocket = SSS->CreateSocket(NAME_Stream, TEXT("FNetworkPlatformFile tcp"));
|
|
|
|
// try to connect to the server
|
|
if (FileSocket->Connect(*Addr) == false)
|
|
{
|
|
// on failure, shut it all down
|
|
SSS->DestroySocket(FileSocket);
|
|
FileSocket = NULL;
|
|
UE_LOG(LogNetworkPlatformFile, Error, TEXT("Failed to connect to file server at %s:%d."), HostIP, (int32)DEFAULT_FILE_SERVING_PORT);
|
|
}
|
|
}
|
|
|
|
// was the socket opened?
|
|
bIsUsable = FileSocket != NULL;
|
|
if (bIsUsable)
|
|
{
|
|
FCommandLine::AddToSubprocessCommandline( *FString::Printf( TEXT("-FileHostIP=%s"), HostIP ) );
|
|
}
|
|
return bIsUsable;
|
|
}
|
|
|
|
void FNetworkPlatformFile::InitializeAfterSetActive()
|
|
{
|
|
double NetworkFileStartupTime = 0.0;
|
|
{
|
|
SCOPE_SECONDS_COUNTER(NetworkFileStartupTime);
|
|
|
|
#if USE_MCSOCKET_FOR_NFS
|
|
MCSocket = new FMultichannelTcpSocket(FileSocket, 64 * 1024 * 1024);
|
|
#endif
|
|
|
|
// 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))
|
|
{
|
|
// on failure, shut it all down
|
|
delete MCSocket;
|
|
MCSocket = NULL;
|
|
ISocketSubsystem::Get()->DestroySocket(FileSocket);
|
|
FileSocket = NULL;
|
|
}
|
|
else
|
|
{
|
|
// receive the cooked version information
|
|
int32 ServerPackageVersion = 0;
|
|
int32 ServerPackageLicenseeVersion = 0;
|
|
ProcessServerInitialResponse(Response, ServerPackageVersion, ServerPackageLicenseeVersion);
|
|
|
|
// 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( RootContentPaths ); RootPathIt; ++RootPathIt )
|
|
{
|
|
const FString& RootPath = *RootPathIt;
|
|
const FString& ContentFolder = FPackageName::LongPackageNameToFilename(RootPath);
|
|
|
|
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
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 (FileSocket != NULL
|
|
&& !GIsRequestingExit) // the socket subsystem is probably already gone, so it will crash if we clean up
|
|
{
|
|
delete FinishedAsyncReadUnsolicitedFiles; // wait here for any async unsolicited files to finish reading being read from the network
|
|
FinishedAsyncReadUnsolicitedFiles = NULL;
|
|
delete FinishedAsyncWriteUnsolicitedFiles; // wait here for any async unsolicited files to finish writing
|
|
FinishedAsyncWriteUnsolicitedFiles = NULL;
|
|
// kill the socket
|
|
delete MCSocket;
|
|
MCSocket = NULL;
|
|
ISocketSubsystem::Get()->DestroySocket(FileSocket);
|
|
}
|
|
}
|
|
|
|
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;
|
|
FPaths::MakeStandardFilename(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)
|
|
{
|
|
// FScopeLock ScopeLock(&SynchronizationObject);
|
|
|
|
FString RelativeFilename = Filename;
|
|
FPaths::MakeStandardFilename(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);
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
FPaths::MakeStandardFilename(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;
|
|
FPaths::MakeStandardFilename(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::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;
|
|
FPaths::MakeStandardFilename(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;
|
|
FPaths::MakeStandardFilename(RelativeFrom);
|
|
|
|
if (!IsInLocalDirectory(RelativeFrom))
|
|
{
|
|
EnsureFileIsLocal(RelativeFrom);
|
|
}
|
|
return InnerPlatformFile->ConvertToAbsolutePathForExternalAppForRead(Filename);
|
|
}
|
|
|
|
FString FNetworkPlatformFile::ConvertToAbsolutePathForExternalAppForWrite( const TCHAR* Filename )
|
|
{
|
|
FString RelativeFrom = Filename;
|
|
FPaths::MakeStandardFilename(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;
|
|
FPaths::MakeStandardFilename(RelativeDirectory);
|
|
FServerTOC::FDirectory* ServerDirectory = ServerFiles.FindDirectory(RelativeDirectory);
|
|
return ServerDirectory != NULL;
|
|
}
|
|
|
|
void FNetworkPlatformFile::GetFileInfo(const TCHAR* Filename, FFileInfo& Info)
|
|
{
|
|
FString RelativeFilename = Filename;
|
|
FPaths::MakeStandardFilename(RelativeFilename);
|
|
|
|
// don't copy files in local directories
|
|
if (!IsInLocalDirectory(RelativeFilename))
|
|
{
|
|
EnsureFileIsLocal(RelativeFilename);
|
|
}
|
|
|
|
// @todo: This is pretty inefficient. If GetFileInfo was a first class function, this could be avoided
|
|
Info.FileExists = InnerPlatformFile->FileExists(Filename);
|
|
Info.ReadOnly = InnerPlatformFile->IsReadOnly(Filename);
|
|
Info.Size = InnerPlatformFile->FileSize(Filename);
|
|
Info.TimeStamp = InnerPlatformFile->GetTimeStamp(Filename);
|
|
Info.AccessTimeStamp = InnerPlatformFile->GetAccessTimeStamp(Filename);
|
|
}
|
|
|
|
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 GameRelPath = FPaths::GameDir();
|
|
TArray<FString> Directories;
|
|
Directories.Add(EngineRelPath);
|
|
Directories.Add(GameRelPath);
|
|
|
|
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 USE_MCSOCKET_FOR_NFS
|
|
if (FNFSMessageHeader::SendPayloadAndReceiveResponse(Payload, Response, FSimpleAbstractSocket_FMultichannelTCPSocket(MCSocket, NFS_Channels::Main)) == false)
|
|
#else
|
|
if (FNFSMessageHeader::SendPayloadAndReceiveResponse(Payload, Response, FSimpleAbstractSocket_FSocket(FileSocket)) == false)
|
|
#endif
|
|
{
|
|
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;
|
|
}
|
|
|
|
|
|
//@todo, this should really be a member of FNetworkPlatformFile
|
|
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())
|
|
{
|
|
Event->Trigger(); // last file, fire trigger
|
|
}
|
|
}
|
|
}
|
|
/** Give the name for external event viewers
|
|
* @return the name to display in external event viewers
|
|
*/
|
|
static const TCHAR *Name()
|
|
{
|
|
return TEXT("FAsyncNetworkWriteWorker");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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 AsyncReadUnsolicitedFile(int32 InNumUnsolictedFiles, FNetworkPlatformFile& InNetworkFile, FScopedEvent* InEvent, IPlatformFile& InInnerPlatformFile,
|
|
FScopedEvent* InAllDoneEvent, FString& InServerEngineDir, FString& InServerGameDir)
|
|
{
|
|
class FAsyncReadUnsolicitedFile : public FNonAbandonableTask
|
|
{
|
|
public:
|
|
int32 NumUnsolictedFiles;
|
|
FNetworkPlatformFile& NetworkFile;
|
|
FScopedEvent* Event;
|
|
IPlatformFile& InnerPlatformFile;
|
|
FScopedEvent* AllDoneEvent;
|
|
FString ServerEngineDir;
|
|
FString ServerGameDir;
|
|
|
|
FAsyncReadUnsolicitedFile(int32 In_NumUnsolictedFiles, FNetworkPlatformFile* In_NetworkFile, FScopedEvent* In_Event, IPlatformFile* In_InnerPlatformFile,
|
|
FScopedEvent* In_AllDoneEvent, FString& In_ServerEngineDir, FString& In_ServerGameDir)
|
|
: NumUnsolictedFiles(In_NumUnsolictedFiles)
|
|
, NetworkFile(*In_NetworkFile)
|
|
, Event(In_Event)
|
|
, InnerPlatformFile(*In_InnerPlatformFile)
|
|
, AllDoneEvent(In_AllDoneEvent)
|
|
, ServerEngineDir(In_ServerEngineDir)
|
|
, ServerGameDir(In_ServerGameDir)
|
|
{
|
|
check(Event);
|
|
check(AllDoneEvent);
|
|
}
|
|
|
|
/** Write the file */
|
|
void DoWork()
|
|
{
|
|
check(!OutstandingAsyncWrites.GetValue());
|
|
OutstandingAsyncWrites.Add(NumUnsolictedFiles);
|
|
for (int32 Index = 0; Index < NumUnsolictedFiles; Index++)
|
|
{
|
|
FArrayReader* UnsolictedResponse = new FArrayReader;
|
|
if (!NetworkFile.ReceivePayload(*UnsolictedResponse))
|
|
{
|
|
UE_LOG(LogNetworkPlatformFile, Fatal, TEXT("Receive failure!"));
|
|
return;
|
|
}
|
|
FString UnsolictedReplyFile;
|
|
*UnsolictedResponse << UnsolictedReplyFile;
|
|
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, AllDoneEvent);
|
|
}
|
|
Event->Trigger();
|
|
}
|
|
/** Give the name for external event viewers
|
|
* @return the name to display in external event viewers
|
|
*/
|
|
static const TCHAR *Name()
|
|
{
|
|
return TEXT("FAsyncReadUnsolicitedFile");
|
|
}
|
|
};
|
|
(new FAutoDeleteAsyncTask<FAsyncReadUnsolicitedFile>(InNumUnsolictedFiles, &InNetworkFile, InEvent, &InInnerPlatformFile, InAllDoneEvent, InServerEngineDir, InServerGameDir))->StartBackgroundTask();
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
delete FinishedAsyncReadUnsolicitedFiles; // wait here for any async unsolicited files to finish reading being read from the network
|
|
FinishedAsyncReadUnsolicitedFiles = NULL;
|
|
delete FinishedAsyncWriteUnsolicitedFiles; // wait here for any async unsolicited files to finish writing
|
|
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
|
|
bool bIsCookable = GConfig && GConfig->IsReadyForUse() && FPackageName::IsPackageExtension(*FPaths::GetExtension(Filename, true));
|
|
|
|
// 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)
|
|
{
|
|
FinishedAsyncReadUnsolicitedFiles = new FScopedEvent;
|
|
FinishedAsyncWriteUnsolicitedFiles = new FScopedEvent;
|
|
AsyncReadUnsolicitedFile(NumUnsolictedFiles, *this, FinishedAsyncReadUnsolicitedFiles, *InnerPlatformFile, FinishedAsyncWriteUnsolicitedFiles, ServerEngineDir, ServerGameDir);
|
|
}
|
|
|
|
ThisTime = 1000.0f * float(FPlatformTime::Seconds() - StartTime);
|
|
//UE_LOG(LogNetworkPlatformFile, Display, TEXT("Write file to local %6.2fms"), ThisTime);
|
|
}
|
|
|
|
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]);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FNetworkPlatformFile::ReceivePayload(FArrayReader& OutPayload)
|
|
{
|
|
if (MCSocket)
|
|
{
|
|
return FNFSMessageHeader::ReceivePayload(OutPayload, FSimpleAbstractSocket_FMultichannelTCPSocket(MCSocket, NFS_Channels::Main));
|
|
}
|
|
return FNFSMessageHeader::ReceivePayload(OutPayload, FSimpleAbstractSocket_FSocket(FileSocket));
|
|
}
|
|
|
|
bool FNetworkPlatformFile::WrapAndSendPayload(const TArray<uint8>& Payload)
|
|
{
|
|
if (MCSocket)
|
|
{
|
|
return FNFSMessageHeader::WrapAndSendPayload(Payload, FSimpleAbstractSocket_FMultichannelTCPSocket(MCSocket, NFS_Channels::Main));
|
|
}
|
|
return FNFSMessageHeader::WrapAndSendPayload(Payload, FSimpleAbstractSocket_FSocket(FileSocket));
|
|
}
|
|
|
|
bool FNetworkPlatformFile::SendPayloadAndReceiveResponse(const TArray<uint8>& Payload, class FArrayReader& Response)
|
|
{
|
|
if (!WrapAndSendPayload(Payload))
|
|
{
|
|
return false;
|
|
}
|
|
delete FinishedAsyncReadUnsolicitedFiles; // wait here for any async unsolicited files to finish being read from the network before we start to read again
|
|
FinishedAsyncReadUnsolicitedFiles = NULL;
|
|
return ReceivePayload(Response);
|
|
}
|
|
|
|
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);
|