You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden ========================== MAJOR FEATURES + CHANGES ========================== Change 2845644 on 2016/01/27 by Martin.Wilson Clear marker sync flag after creating tick record, add more information to checks incase issue occurs again #Jira OR-13469 #rb Thomas.Sarkanen #tests in editor tests, bot match. Change 2845613 on 2016/01/27 by John.Pollard Latest network profiler binaries #rb none #tests run profiler Change 2845595 on 2016/01/27 by Mieszko.Zielinski Fixed pathfollowing's block detection using wrong distance when testing for blockage #UE4 #rb Lukasz.Furman #test golden path Change 2845593 on 2016/01/27 by Jeff.Farris Added support for setting and choosing filmbacks and lenses for cinematic cameras. - New CineCameraComponent and CineCameraActor classes - can define filmback and lens presets via ini file - details customizations for filmback and lens selection - added prototype set of filmbacks and lenses (primes and zooms) - Camera details customization now gracefully handles when CameraSettings category is hidden - example sequencer usage is content/developers/jeff.farris/CineCams/CineCamTestMap #rb none #tests editor Change 2845585 on 2016/01/27 by Marcus.Wassmer Don't fool with connected state if we're early outing from the OS intercepting controller events. This fixes some missing delegates. Fixes cert bug about controller disconnect screen staying up permanently #rb Cody.Haskell #test Turning off controller, turning on again. #lockdown Andrew.Grant Change 2845528 on 2016/01/27 by Max.Chen Sequencer: Fix new spawnables not immediately getting an object binding. This was resulted in a missing +Track->Animation when first creating a spawnable and duplicate transform keys. #jira UE-26084 #tests Add spawnable, +Track->Animation exists #rb none Change 2845483 on 2016/01/27 by Andrew.Rodham Sequencer: Fixed MaximizedViewport not getting cleared/restored correctly #jria UE-26016 #rb Max.Chen #tests Tested the viewports Change 2845421 on 2016/01/27 by Max.Preussner Sequencer: Implemented go-to feature #RB max.chen #TESTS Editor Change 2845407 on 2016/01/27 by Max.Preussner Sequencer: Moved SetViewRange() into ISequencer and made it public #RB max.chen #TESTS none Change 2845404 on 2016/01/27 by Andrew.Rodham Sequencer: Fixed cinematic viewport not updating when dragging transport range #jira UE-26003 #rb Max.Chen #tests Scrubbed the timeline Change 2845396 on 2016/01/27 by David.Nikdel #OSS #Purchase #Store #PS4 - Minor log cleanup #RB: none #TESTS: compiles Change 2845375 on 2016/01/27 by Max.Chen Sequencer: Implement cinematic shot track thumbnails. #jira UE-25125 #tests Rebuild the trailer with the cinematic shot track #rb none Change 2845359 on 2016/01/27 by Marcus.Wassmer Downgrade some checks to ensures. #rb none #test ps4 Change 2845347 on 2016/01/27 by Nicholas.Davies Remove unused EditorStyle dependency from Social. It is not being used, and causes issues for the engine team. #RB Antony.Carter #TESTS n/a #codereview Robert.Manuszewski Change 2845227 on 2016/01/27 by Robert.Manuszewski Adding flags to create callstack map files when building Arxan protection #rb none #tests Built arxan exe Change 2844871 on 2016/01/26 by Andrew.Grant Prevent enums from being regenerated while cooking (prevents false-positive warning about FText's being regenerated) #rb none #tests ran editor [CL 2847722 by Andrew Grant in Main branch]
1296 lines
41 KiB
C++
1296 lines
41 KiB
C++
// Copyright 1998-2016 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");
|
|
FString FNetworkPlatformFile::BulkFileExtension = TEXT(".ubulk");
|
|
|
|
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"));
|
|
|
|
if (InnerPlatformFile->GetLowerLevel())
|
|
{
|
|
InnerPlatformFile->GetLowerLevel()->AddLocalDirectories(LocalDirectories);
|
|
}
|
|
else
|
|
{
|
|
InnerPlatformFile->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) && (Extension != BulkFileExtension))
|
|
{
|
|
// 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);
|