2014-03-14 14:13:41 -04:00
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
# include "NetworkFilePrivatePCH.h"
# include "NetworkPlatformFile.h"
# include "Sockets.h"
2014-05-07 19:32:02 -04:00
# include "MultichannelTCP.h"
2014-03-14 14:13:41 -04:00
# 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 ) ;