2022-08-12 15:13:26 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "Perforce.h"
# include "Misc/FileHelper.h"
# include "Misc/Paths.h"
# include "Algo/Sort.h"
# include "Algo/Transform.h"
# include "HAL/PlatformProcess.h"
# include "HAL/Event.h"
# include "Utility.h"
namespace UGSCore
{
bool TryGetValue ( const TMap < FString , FString > & Map , const TCHAR * Key , FString & OutValue )
{
const FString * Value = Map . Find ( Key ) ;
if ( Value = = nullptr )
{
OutValue = FString ( ) ;
return false ;
}
else
{
OutValue = * Value ;
return true ;
}
}
TArray < FString > Split ( const FString & Text , const TCHAR * Delim , bool bRemoveEmptyEntries = false )
{
TArray < FString > Result ;
Text . ParseIntoArray ( Result , Delim , bRemoveEmptyEntries ) ;
return Result ;
}
FString GetTempFileName ( )
{
return FPaths : : EngineIntermediateDir ( ) / FGuid : : NewGuid ( ) . ToString ( ) ;
}
struct FPerforceExe
{
bool bValidP4 = false ;
// Default assumed locations, we will try to find the real path
# if PLATFORM_WINDOWS
FString ExecutablePath = TEXT ( " C: \\ Program Files (x86) \\ Perforce \\ p4.exe " ) ;
# else
FString ExecutablePath = TEXT ( " /usr/bin/p4 " ) ;
# endif
FString GetPerforceExe ( )
{
static bool bExecutablePathCached = false ;
if ( bExecutablePathCached )
{
return ExecutablePath ;
}
bExecutablePathCached = true ;
# if PLATFORM_MAC || PLATFORM_LINUX
int32 ReturnCode = - 1 ;
FString OutResults ;
FString OutErrors ;
# if PLATFORM_MAC
FPlatformProcess : : ExecProcess ( TEXT ( " /usr/bin/mdfind " ) , TEXT ( " \" kMDItemFSName = 'p4' && kMDItemContentType = 'public.unix-executable' \" " ) , & ReturnCode , & OutResults , & OutErrors ) ;
2022-09-13 18:50:03 -04:00
// in case it returns multiple p4's, pick the first one
OutResults = Split ( OutResults , LINE_TERMINATOR ) [ 0 ] ;
2022-08-12 15:13:26 -04:00
# elif PLATFORM_LINUX
FPlatformProcess : : ExecProcess ( TEXT ( " which " ) , TEXT ( " p4 " ) , & ReturnCode , & OutResults , & OutErrors ) ;
# endif // PLATFORM_MAC
if ( ReturnCode ! = 0 )
{
// Todo: Log OutErrors somehow (should this take function the output log?)
// for now just return our default paths
return ExecutablePath ;
}
ExecutablePath = OutResults . TrimEnd ( ) ;
# endif // PLATFORM_MAC || PLATFORM_LINUX
return ExecutablePath ;
}
2022-10-11 11:51:56 -04:00
inline int InnerRunCommand ( const FString & CommandLine , TArray < FString > & OutLines , FEvent * AbortEvent , const FString WriteChildText = FString ( ) )
2022-08-12 15:13:26 -04:00
{
2022-10-11 11:51:56 -04:00
void * ParentReadPipe = nullptr ;
void * ParentWritePipe = nullptr ;
void * ChildReadPipe = nullptr ;
void * ChildWritePipe = nullptr ;
2022-08-12 15:13:26 -04:00
2022-10-11 11:51:56 -04:00
// Create pipe for reading from child stdout
FPlatformProcess : : CreatePipe ( ParentReadPipe , ChildWritePipe ) ;
2022-08-12 15:13:26 -04:00
2022-10-11 11:51:56 -04:00
// Create pipe for writing to child stdin
const bool bWriting = ! WriteChildText . IsEmpty ( ) ;
if ( bWriting )
{
FPlatformProcess : : CreatePipe ( ChildReadPipe , ParentWritePipe ) ;
}
FProcHandle P4Proc = FPlatformProcess : : CreateProc ( * GetPerforceExe ( ) , * CommandLine , false , true , true , nullptr , 0 , nullptr , ChildWritePipe , ChildReadPipe ) ;
// Write to child process's stdin
if ( FPlatformProcess : : IsProcRunning ( P4Proc ) & & bWriting )
{
FPlatformProcess : : WritePipe ( ParentWritePipe , WriteChildText ) ;
}
// Done writing
FPlatformProcess : : ClosePipe ( ChildReadPipe , ParentWritePipe ) ;
// Read from child process's stdout
2022-08-12 15:13:26 -04:00
FString P4Output ;
2022-10-11 11:51:56 -04:00
FString LatestOutput = FPlatformProcess : : ReadPipe ( ParentReadPipe ) ;
2022-08-12 15:13:26 -04:00
while ( FPlatformProcess : : IsProcRunning ( P4Proc ) | | ! LatestOutput . IsEmpty ( ) )
{
P4Output + = LatestOutput ;
2022-10-11 11:51:56 -04:00
LatestOutput = FPlatformProcess : : ReadPipe ( ParentReadPipe ) ;
2022-08-16 18:20:22 -04:00
if ( AbortEvent - > Wait ( FTimespan : : Zero ( ) ) )
{
FPlatformProcess : : TerminateProc ( P4Proc ) ;
return - 1 ;
}
2022-08-12 15:13:26 -04:00
}
2022-10-11 11:51:56 -04:00
// Done reading
FPlatformProcess : : ClosePipe ( ParentReadPipe , ChildWritePipe ) ;
2022-08-12 15:13:26 -04:00
OutLines = Split ( P4Output , LINE_TERMINATOR ) ;
int ExitCode = - 1 ;
2022-10-11 11:51:56 -04:00
const bool bGotReturnCode = FPlatformProcess : : GetProcReturnCode ( P4Proc , & ExitCode ) ;
if ( bGotReturnCode )
2022-08-12 15:13:26 -04:00
{
return ExitCode ;
}
return - 1 ;
}
2022-10-11 11:51:56 -04:00
int RunCommand ( const FString & CommandLine , TArray < FString > & OutLines , FEvent * AbortEvent , const FString & WritePipeText = FString ( ) )
2022-08-12 15:13:26 -04:00
{
if ( bValidP4 )
{
2022-10-11 11:51:56 -04:00
return InnerRunCommand ( CommandLine , OutLines , AbortEvent , WritePipeText ) ;
2022-08-12 15:13:26 -04:00
}
return - 1 ;
}
void VerifyPerforcePath ( )
{
# if PLATFORM_WINDOWS
bValidP4 = FPaths : : FileExists ( ExecutablePath ) ;
if ( ! bValidP4 )
{
ExecutablePath . ReplaceInline ( TEXT ( " (x86) " ) , TEXT ( " " ) ) ;
bValidP4 = FPaths : : FileExists ( ExecutablePath ) ;
}
else
# endif
{
bValidP4 = true ;
}
}
FPerforceExe ( )
{
VerifyPerforcePath ( ) ;
}
} ;
static FPerforceExe GPerforceExe ;
//// FPerforceChangeSummary ////
FPerforceChangeSummary : : FPerforceChangeSummary ( )
: Number ( 0 )
, Date ( 0 )
{
}
//// FPerforceFileChangeSummary ////
FPerforceFileChangeSummary : : FPerforceFileChangeSummary ( )
: Revision ( 0 )
, ChangeNumber ( 0 )
, Date ( 0 )
{
}
//// FPerforceClientRecord ////
FPerforceClientRecord : : FPerforceClientRecord ( const TMap < FString , FString > & Tags )
{
TryGetValue ( Tags , TEXT ( " client " ) , Name ) ;
TryGetValue ( Tags , TEXT ( " Owner " ) , Owner ) ;
TryGetValue ( Tags , TEXT ( " Host " ) , Host ) ;
TryGetValue ( Tags , TEXT ( " Root " ) , Root ) ;
}
//// FPerforceDescribeFileRecord ////
FPerforceDescribeFileRecord : : FPerforceDescribeFileRecord ( )
: Revision ( 0 )
, FileSize ( 0 )
{
}
//// FPerforceDescribeRecord ////
FPerforceDescribeRecord : : FPerforceDescribeRecord ( const TMap < FString , FString > & Tags )
{
FString ChangeString ;
if ( ! TryGetValue ( Tags , TEXT ( " change " ) , ChangeString ) | | ! FUtility : : TryParse ( * ChangeString , ChangeNumber ) )
{
ChangeNumber = - 1 ;
}
TryGetValue ( Tags , TEXT ( " user " ) , User ) ;
TryGetValue ( Tags , TEXT ( " client " ) , Client ) ;
FString TimeString ;
if ( ! TryGetValue ( Tags , TEXT ( " time " ) , TimeString ) | | ! FUtility : : TryParse ( * TimeString , Time ) )
{
Time = - 1 ;
}
TryGetValue ( Tags , TEXT ( " desc " ) , Description ) ;
TryGetValue ( Tags , TEXT ( " status " ) , Status ) ;
TryGetValue ( Tags , TEXT ( " changeType " ) , ChangeType ) ;
TryGetValue ( Tags , TEXT ( " path " ) , Path ) ;
for ( int Idx = 0 ; ; Idx + + )
{
FString Suffix = FString : : Printf ( TEXT ( " %d " ) , Idx ) ;
FPerforceDescribeFileRecord File ;
if ( ! TryGetValue ( Tags , * ( FString ( TEXT ( " depotFile " ) ) + Suffix ) , File . DepotFile ) )
{
break ;
}
TryGetValue ( Tags , * ( FString ( TEXT ( " action " ) ) + Suffix ) , File . Action ) ;
TryGetValue ( Tags , * ( FString ( TEXT ( " type " ) ) + Suffix ) , File . Type ) ;
FString RevisionString ;
if ( ! TryGetValue ( Tags , * ( FString ( TEXT ( " rev " ) ) + Suffix ) , RevisionString ) | | ! FUtility : : TryParse ( * RevisionString , File . Revision ) )
{
File . Revision = - 1 ;
}
FString FileSizeString ;
if ( ! TryGetValue ( Tags , * ( FString ( TEXT ( " fileSize " ) ) + Suffix ) , FileSizeString ) | | ! FUtility : : TryParse ( * FileSizeString , File . FileSize ) )
{
File . FileSize = - 1 ;
}
TryGetValue ( Tags , * ( FString ( TEXT ( " digest " ) ) + Suffix ) , File . Digest ) ;
Files . Add ( File ) ;
}
}
//// FPerforceInfoRecord ////
FPerforceInfoRecord : : FPerforceInfoRecord ( const TMap < FString , FString > & Tags )
: ServerTimeZone ( 0 , 0 , 0 )
{
TryGetValue ( Tags , TEXT ( " userName " ) , UserName ) ;
TryGetValue ( Tags , TEXT ( " clientHost " ) , HostName ) ;
TryGetValue ( Tags , TEXT ( " clientAddress " ) , ClientAddress ) ;
TryGetValue ( Tags , TEXT ( " serverAddress " ) , ServerAddress ) ;
FString ServerDateTime ;
if ( TryGetValue ( Tags , TEXT ( " serverDate " ) , ServerDateTime ) )
{
TArray < FString > Fields ;
ServerDateTime . ParseIntoArray ( Fields , TEXT ( " " ) ) ;
if ( Fields . Num ( ) > = 3 )
{
int32 Offset ;
if ( FUtility : : TryParse ( * Fields [ 2 ] , Offset ) )
{
ServerTimeZone = ( Offset < 0 ) ? - FTimespan ( - Offset / 100 , - Offset % 100 , 0 ) : FTimespan ( Offset / 100 , Offset % 100 , 0 ) ;
}
}
}
}
//// FPerforceFileRecord ////
FPerforceFileRecord : : FPerforceFileRecord ( const TMap < FString , FString > & Tags )
: Revision ( 0 )
{
TryGetValue ( Tags , TEXT ( " depotFile " ) , DepotPath ) ;
TryGetValue ( Tags , TEXT ( " clientFile " ) , ClientPath ) ;
TryGetValue ( Tags , TEXT ( " path " ) , Path ) ;
if ( ! TryGetValue ( Tags , TEXT ( " action " ) , Action ) )
{
TryGetValue ( Tags , TEXT ( " headAction " ) , Action ) ;
}
IsMapped = Tags . Contains ( TEXT ( " isMapped " ) ) ;
Unmap = Tags . Contains ( TEXT ( " unmap " ) ) ;
FString RevisionString ;
if ( TryGetValue ( Tags , TEXT ( " rev " ) , RevisionString ) )
{
FUtility : : TryParse ( * RevisionString , Revision ) ;
}
}
//// FPerforceTagRecordParser ////
FPerforceTagRecordParser : : FPerforceTagRecordParser ( TFunction < void ( const TMap < FString , FString > & ) > InOutputRecord )
: OutputRecord ( InOutputRecord )
{
}
FPerforceTagRecordParser : : ~ FPerforceTagRecordParser ( )
{
Flush ( ) ;
}
void FPerforceTagRecordParser : : OutputLine ( const FString & Line )
{
int SpaceIdx ;
if ( ! Line . FindChar ( TEXT ( ' ' ) , SpaceIdx ) )
{
SpaceIdx = INDEX_NONE ;
}
FString Key = ( SpaceIdx > 0 ) ? Line . Left ( SpaceIdx ) : Line ;
if ( Tags . Contains ( Key ) )
{
OutputRecord ( Tags ) ;
Tags . Empty ( ) ;
}
Tags . FindOrAdd ( Key ) = ( SpaceIdx > 0 ) ? Line . Mid ( SpaceIdx + 1 ) : TEXT ( " " ) ;
}
void FPerforceTagRecordParser : : Flush ( )
{
if ( Tags . Num ( ) > 0 )
{
OutputRecord ( Tags ) ;
Tags . Empty ( ) ;
}
}
//// FPerforceSyncOptions ////
FPerforceSyncOptions : : FPerforceSyncOptions ( )
: NumRetries ( 0 )
, NumThreads ( 0 )
, TcpBufferSize ( 0 )
{
}
//// FPerforceSpec ////
const TCHAR * FPerforceSpec : : GetField ( const TCHAR * Name ) const
{
for ( const TTuple < FString , FString > & Section : Sections )
{
if ( Section . Key = = Name )
{
return * Section . Value ;
}
}
return nullptr ;
}
void FPerforceSpec : : SetField ( const TCHAR * Name , const TCHAR * Value )
{
for ( TTuple < FString , FString > & Section : Sections )
{
if ( Section . Key = = Name )
{
Section . Value = Value ;
return ;
}
}
Sections . Add ( TTuple < FString , FString > ( Name , Value ) ) ;
}
bool FPerforceSpec : : TryParse ( const TArray < FString > & Lines , TSharedPtr < FPerforceSpec > & OutSpec , FOutputDevice & Log )
{
TSharedPtr < FPerforceSpec > Spec = MakeShared < FPerforceSpec > ( ) ;
for ( int LineIdx = 0 ; LineIdx < Lines . Num ( ) ; LineIdx + + )
{
FString Line = Lines [ LineIdx ] . TrimStartAndEnd ( ) ;
if ( Line . Len ( ) > 0 & & Lines [ LineIdx ] [ 0 ] ! = ' # ' )
{
// Read the section name
int SeparatorIdx ;
if ( ! Lines [ LineIdx ] . FindChar ( ' : ' , SeparatorIdx ) )
{
SeparatorIdx = - 1 ;
}
if ( SeparatorIdx = = - 1 | | ! FChar : : IsAlpha ( Lines [ LineIdx ] [ 0 ] ) )
{
Log . Logf ( TEXT ( " Invalid spec format at line %d: \" %s \" " ) , LineIdx , * Lines [ LineIdx ] ) ;
return false ;
}
// Get the section name
FString SectionName = Lines [ LineIdx ] . Mid ( 0 , SeparatorIdx ) ;
// Parse the section value
FString Value = Lines [ LineIdx ] . Mid ( SeparatorIdx + 1 ) . TrimStartAndEnd ( ) ;
for ( ; LineIdx + 1 < Lines . Num ( ) ; LineIdx + + )
{
if ( Lines [ LineIdx + 1 ] . Len ( ) = = 0 )
{
Value + = TEXT ( " \n " ) ;
}
else if ( Lines [ LineIdx + 1 ] [ 0 ] = = ' \t ' )
{
Value + = Lines [ LineIdx + 1 ] . Mid ( 1 ) + TEXT ( " \n " ) ;
}
else
{
break ;
}
}
Value . TrimEndInline ( ) ;
Spec - > Sections . Add ( TTuple < FString , FString > ( SectionName , Value ) ) ;
}
}
OutSpec = Spec ;
return true ;
}
FString FPerforceSpec : : ToString ( ) const
{
FString Result ;
for ( const TTuple < FString , FString > & Section : Sections )
{
if ( Section . Value . Contains ( TEXT ( " \n " ) ) )
{
Result + = Section . Key + TEXT ( " : \n \t " ) + Section . Value . Replace ( TEXT ( " \n " ) , TEXT ( " \n \t " ) ) + LINE_TERMINATOR ;
}
else
{
Result + = Section . Key + TEXT ( " : \t " ) + Section . Value + LINE_TERMINATOR ;
}
Result + = LINE_TERMINATOR ;
}
return Result ;
}
//// FPerforceOutputLine ////
FPerforceOutputLine : : FPerforceOutputLine ( )
: Channel ( EPerforceOutputChannel : : Unknown )
{
}
FPerforceOutputLine : : FPerforceOutputLine ( EPerforceOutputChannel InChannel , const FString & InText )
: Channel ( InChannel )
, Text ( InText )
{
}
//// FPerforceConnection ////
FPerforceConnection : : FPerforceConnection ( const TCHAR * InUserName , const TCHAR * InClientName , const TCHAR * InServerAndPort )
: ServerAndPort ( ( InServerAndPort ! = nullptr ) ? InServerAndPort : TEXT ( " " ) )
, UserName ( ( InUserName ! = nullptr ) ? InUserName : TEXT ( " " ) )
, ClientName ( ( InClientName ! = nullptr ) ? InClientName : TEXT ( " " ) )
{
}
TSharedRef < FPerforceConnection > FPerforceConnection : : OpenClient ( const FString & NewClientName ) const
{
return MakeShared < FPerforceConnection > ( * UserName , * NewClientName , * ServerAndPort ) ;
}
bool FPerforceConnection : : Info ( TSharedPtr < FPerforceInfoRecord > & OutInfo , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TArray < TMap < FString , FString > > TagRecords ;
if ( ! RunCommand ( TEXT ( " info -s " ) , TagRecords , ECommandOptions : : NoClient , AbortEvent , Log ) | | TagRecords . Num ( ) ! = 1 )
{
OutInfo = TSharedPtr < FPerforceInfoRecord > ( ) ;
return false ;
}
else
{
OutInfo = MakeShared < FPerforceInfoRecord > ( TagRecords [ 0 ] ) ;
return true ;
}
}
bool FPerforceConnection : : GetSetting ( const FString & Name , FString & Value , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TArray < FString > Lines ;
if ( ! RunCommand ( FString : : Printf ( TEXT ( " set %s " ) , * Name ) , Lines , ECommandOptions : : NoChannels , AbortEvent , Log ) | | Lines . Num ( ) ! = 1 )
{
Value = FString ( ) ;
return false ;
}
if ( Lines [ 0 ] . Len ( ) < = Name . Len ( ) | | ! Lines [ 0 ] . StartsWith ( Name ) | | Lines [ 0 ] [ Name . Len ( ) ] ! = ' = ' )
{
Value = FString ( ) ;
return false ;
}
Value = Lines [ 0 ] . Mid ( Name . Len ( ) + 1 ) ;
int EndIdx = Value . Find ( TEXT ( " ( " ) ) ;
if ( EndIdx ! = INDEX_NONE )
{
Value = Value . Mid ( 0 , EndIdx ) . TrimStartAndEnd ( ) ;
}
return true ;
}
bool FPerforceConnection : : FindClients ( TArray < FPerforceClientRecord > & Clients , FEvent * AbortEvent , FOutputDevice & Log ) const
{
return RunCommand ( TEXT ( " clients " ) , Clients , ECommandOptions : : NoClient , AbortEvent , Log ) ;
}
2022-09-29 13:55:06 -04:00
bool FPerforceConnection : : FindClients ( TArray < FPerforceClientRecord > & Clients , const FString & ForUserName , FEvent * AbortEvent , FOutputDevice & Log ) const
2022-08-12 15:13:26 -04:00
{
TArray < FString > Lines ;
2022-09-29 13:55:06 -04:00
if ( ! RunCommand ( FString : : Printf ( TEXT ( " clients -u%s " ) , * ForUserName ) , Clients , ECommandOptions : : NoClient , AbortEvent , Log ) )
2022-08-12 15:13:26 -04:00
{
return false ;
}
for ( const FString & Line : Lines )
{
TArray < FString > Tokens = Split ( Line , TEXT ( " " ) , true ) ;
if ( Tokens . Num ( ) < 5 | | Tokens [ 0 ] ! = TEXT ( " Client " ) | | Tokens [ 3 ] ! = TEXT ( " root " ) )
{
Log . Logf ( TEXT ( " Couldn't parse client from line '%s' " ) , * Line ) ;
}
}
return true ;
}
bool FPerforceConnection : : TryGetClientSpec ( const FString & InClientName , TSharedPtr < FPerforceSpec > & OutSpec , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TArray < FString > Lines ;
if ( ! RunCommand ( FString : : Printf ( TEXT ( " client -o %s " ) , * InClientName ) , Lines , ECommandOptions : : None , AbortEvent , Log ) )
{
OutSpec = nullptr ;
return false ;
}
if ( ! FPerforceSpec : : TryParse ( Lines , OutSpec , Log ) )
{
OutSpec = nullptr ;
return false ;
}
return true ;
}
2022-10-11 11:51:56 -04:00
bool FPerforceConnection : : CreateClient ( const FPerforceClientRecord & Client , const FString & Stream , FEvent * AbortEvent , FOutputDevice & Log ) const
{
const FString PipeInput = FString : : Printf (
TEXT ( " Client: %s \n Stream: %s \n Root: %s \n Owner: %s \n Host: %s \n Description: Created by %s in SlateUGS \n " ) ,
* Client . Name , * Stream , * Client . Root , * Client . Owner , * Client . Host , * Client . Owner ) ;
return RunCommand ( TEXT ( " client -i " ) , ECommandOptions : : None , AbortEvent , Log , PipeInput ) ;
}
2022-08-12 15:13:26 -04:00
bool FPerforceConnection : : TryGetStreamSpec ( const FString & StreamName , TSharedPtr < FPerforceSpec > & OutSpec , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TArray < FString > Lines ;
if ( ! RunCommand ( FString : : Printf ( TEXT ( " stream -o %s " ) , * StreamName ) , Lines , ECommandOptions : : None , AbortEvent , Log ) )
{
OutSpec = nullptr ;
return false ;
}
if ( ! FPerforceSpec : : TryParse ( Lines , OutSpec , Log ) )
{
OutSpec = nullptr ;
return false ;
}
return true ;
}
bool FPerforceConnection : : FindFiles ( const FString & Filter , TArray < FPerforceFileRecord > & OutFileRecords , FEvent * AbortEvent , FOutputDevice & Log ) const
{
return RunCommand ( FString : : Printf ( TEXT ( " fstat \" %s \" " ) , * Filter ) , OutFileRecords , ECommandOptions : : None , AbortEvent , Log ) ;
}
bool FPerforceConnection : : Print ( const FString & DepotPath , TArray < FString > & OutLines , FEvent * AbortEvent , FOutputDevice & Log ) const
{
FString TempFileName = GetTempFileName ( ) ;
if ( ! PrintToFile ( DepotPath , TempFileName , AbortEvent , Log ) )
{
return false ;
}
else
{
return FFileHelper : : LoadFileToStringArray ( OutLines , * TempFileName ) ;
}
}
bool FPerforceConnection : : PrintToFile ( const FString & DepotPath , const FString & OutputFileName , FEvent * AbortEvent , FOutputDevice & Log ) const
{
return RunCommand ( FString : : Printf ( TEXT ( " print -q -o \" %s \" \" %s \" " ) , * OutputFileName , * DepotPath ) , ECommandOptions : : None , AbortEvent , Log ) ;
}
bool FPerforceConnection : : FileExists ( const FString & Filter , bool & bExists , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TArray < FPerforceFileRecord > FileRecords ;
if ( RunCommand ( FString : : Printf ( TEXT ( " fstat \" %s \" " ) , * Filter ) , FileRecords , ECommandOptions : : IgnoreNoSuchFilesError | ECommandOptions : : IgnoreFilesNotInClientViewError , AbortEvent , Log ) )
{
bExists = ( FileRecords . Num ( ) > 0 ) ;
return true ;
}
else
{
bExists = false ;
return false ;
}
}
bool FPerforceConnection : : FindChanges ( const FString & Filter , int MaxResults , TArray < FPerforceChangeSummary > & OutChanges , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TArray < FString > Filters ;
Filters . Add ( Filter ) ;
return FindChanges ( Filters , MaxResults , OutChanges , AbortEvent , Log ) ;
}
bool FPerforceConnection : : FindChanges ( const TArray < FString > & Filters , int MaxResults , TArray < FPerforceChangeSummary > & OutChanges , FEvent * AbortEvent , FOutputDevice & Log ) const
{
OutChanges . Empty ( ) ;
FString Arguments = TEXT ( " changes -s submitted -t -L " ) ;
if ( MaxResults > 0 )
{
Arguments + = FString : : Printf ( TEXT ( " -m %d " ) , MaxResults ) ;
}
for ( const FString & Filter : Filters )
{
Arguments + = FString : : Printf ( TEXT ( " \" %s \" " ) , * Filter ) ;
}
TArray < FString > Lines ;
if ( ! RunCommand ( Arguments , Lines , ECommandOptions : : None , AbortEvent , Log ) )
{
return false ;
}
for ( int Idx = 0 ; Idx < Lines . Num ( ) ; Idx + + )
{
FPerforceChangeSummary Change ;
if ( ! TryParseChangeSummary ( Lines [ Idx ] , Change ) )
{
Log . Logf ( TEXT ( " Couldn't parse description from '%s' " ) , * Lines [ Idx ] ) ;
}
else
{
for ( ; Idx + 1 < Lines . Num ( ) ; Idx + + )
{
if ( Lines [ Idx + 1 ] . Len ( ) = = 0 )
{
Change . Description + = TEXT ( " \n " ) ;
}
else if ( Lines [ Idx + 1 ] . StartsWith ( " \t " ) )
{
Change . Description + = Lines [ Idx + 1 ] . Mid ( 1 ) + TEXT ( " \n " ) ;
}
else
{
break ;
}
}
Change . Description = Change . Description . TrimStartAndEnd ( ) ;
OutChanges . Add ( Change ) ;
}
}
Algo : : Sort ( OutChanges , [ ] ( const FPerforceChangeSummary & A , const FPerforceChangeSummary & B ) { return A . Number > B . Number ; } ) ;
for ( int Idx = OutChanges . Num ( ) - 1 ; Idx > 0 ; Idx - - )
{
if ( OutChanges [ Idx - 1 ] . Number = = OutChanges [ Idx ] . Number )
{
OutChanges . RemoveAt ( Idx ) ;
}
}
if ( MaxResults > = 0 & & MaxResults < OutChanges . Num ( ) )
{
OutChanges . RemoveAt ( MaxResults , OutChanges . Num ( ) - MaxResults ) ;
}
return true ;
}
bool FPerforceConnection : : TryParseChangeSummary ( const FString & Line , FPerforceChangeSummary & Change ) const
{
TArray < FString > Tokens = Split ( Line , TEXT ( " " ) , true ) ;
if ( Tokens . Num ( ) = = 7 & & Tokens [ 0 ] = = " Change " & & Tokens [ 2 ] = = " on " & & Tokens [ 5 ] = = " by " )
{
if ( FUtility : : TryParse ( * Tokens [ 1 ] , Change . Number ) & & FPerforceUtils : : TryParseDateTime ( Tokens [ 3 ] , Tokens [ 4 ] , Change . Date ) )
{
int UserClientIdx ;
if ( Tokens [ 6 ] . FindChar ( TEXT ( ' @ ' ) , UserClientIdx ) )
{
Change . User = Tokens [ 6 ] . Mid ( 0 , UserClientIdx ) ;
Change . Client = Tokens [ 6 ] . Mid ( UserClientIdx + 1 ) ;
return true ;
}
}
}
return false ;
}
bool FPerforceConnection : : FindFileChanges ( const FString & FilePath , int MaxResults , TArray < FPerforceFileChangeSummary > & Changes , FEvent * AbortEvent , FOutputDevice & Log ) const
{
FString Arguments = TEXT ( " filelog -L -t " ) ;
if ( MaxResults > 0 )
{
Arguments + = FString : : Printf ( TEXT ( " -m %d " ) , MaxResults ) ;
}
Arguments + = FString : : Printf ( TEXT ( " \" %s \" " ) , * FilePath ) ;
TArray < FString > Lines ;
if ( ! RunCommand ( Arguments , EPerforceOutputChannel : : TaggedInfo , Lines , ECommandOptions : : None , AbortEvent , Log ) )
{
return false ;
}
for ( int Idx = 0 ; Idx < Lines . Num ( ) ; Idx + + )
{
FPerforceFileChangeSummary Change ;
if ( ! TryParseFileChangeSummary ( Lines , Idx , Change ) )
{
Log . Logf ( TEXT ( " Couldn't parse description from '%s' " ) , * Lines [ Idx ] ) ;
}
else
{
Changes . Add ( Change ) ;
}
}
return true ;
}
bool FPerforceConnection : : TryParseFileChangeSummary ( const TArray < FString > & Lines , int & LineIdx , FPerforceFileChangeSummary & OutChange ) const
{
2022-09-01 17:34:28 -04:00
TArray < FString > Tokens = Split ( Lines [ LineIdx ] . TrimStartAndEnd ( ) , TEXT ( " " ) , true ) ;
2022-08-12 15:13:26 -04:00
if ( Tokens . Num ( ) ! = 10 | | ! Tokens [ 0 ] . StartsWith ( TEXT ( " # " ) ) | | Tokens [ 1 ] ! = TEXT ( " change " ) | | Tokens [ 4 ] ! = TEXT ( " on " ) | | Tokens [ 7 ] ! = TEXT ( " by " ) )
{
return false ;
}
// Replace [5] date and [6] time with . as FDateTime expects format to be (yyyy.mm.dd-hh.mm.ss)
Tokens [ 5 ] . ReplaceCharInline ( ' / ' , ' . ' ) ;
Tokens [ 6 ] . ReplaceCharInline ( ' : ' , ' . ' ) ;
if ( ! FUtility : : TryParse ( * Tokens [ 0 ] . Mid ( 1 ) , OutChange . Revision ) | | ! FUtility : : TryParse ( * Tokens [ 2 ] , OutChange . ChangeNumber ) | | ! FDateTime : : Parse ( Tokens [ 5 ] + TEXT ( " - " ) + Tokens [ 6 ] , OutChange . Date ) )
{
return false ;
}
int UserClientIdx ;
2022-09-01 17:34:28 -04:00
if ( ! Tokens [ 8 ] . FindChar ( ' @ ' , UserClientIdx ) )
2022-08-12 15:13:26 -04:00
{
return false ;
}
OutChange . Action = Tokens [ 3 ] ;
OutChange . Type = Tokens [ 9 ] ;
if ( OutChange . Type . StartsWith ( TEXT ( " ( " ) ) )
{
OutChange . Type = OutChange . Type . Mid ( 1 ) ;
}
if ( OutChange . Type . EndsWith ( TEXT ( " ) " ) ) )
{
OutChange . Type = OutChange . Type . Left ( OutChange . Type . Len ( ) - 1 ) ;
}
OutChange . User = Tokens [ 8 ] . Mid ( 0 , UserClientIdx ) ;
OutChange . Client = Tokens [ 8 ] . Mid ( UserClientIdx + 1 ) ;
FString Description ;
for ( ; LineIdx + 1 < Lines . Num ( ) ; LineIdx + + )
{
if ( Lines [ LineIdx + 1 ] . Len ( ) = = 0 )
{
Description + = TEXT ( " \n " ) ;
}
else if ( Lines [ LineIdx + 1 ] . StartsWith ( " \t " ) )
{
Description + = Lines [ LineIdx + 1 ] . Mid ( 1 ) + TEXT ( " \n " ) ;
}
else
{
break ;
}
}
Description . TrimStartAndEndInline ( ) ;
OutChange . Description = Description ;
return true ;
}
bool FPerforceConnection : : ConvertToClientPath ( const FString & FileName , FString & OutClientFileName , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TSharedPtr < FPerforceWhereRecord > WhereRecord ;
if ( Where ( FileName , WhereRecord , AbortEvent , Log ) )
{
OutClientFileName = WhereRecord - > ClientPath ;
return true ;
}
else
{
OutClientFileName = TEXT ( " " ) ;
return false ;
}
}
bool FPerforceConnection : : ConvertToDepotPath ( const FString & FileName , FString & OutDepotFileName , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TSharedPtr < FPerforceWhereRecord > WhereRecord ;
if ( Where ( FileName , WhereRecord , AbortEvent , Log ) )
{
OutDepotFileName = WhereRecord - > DepotPath ;
return true ;
}
else
{
OutDepotFileName = TEXT ( " " ) ;
return false ;
}
}
bool FPerforceConnection : : ConvertToLocalPath ( const FString & FileName , FString & OutLocalFileName , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TSharedPtr < FPerforceWhereRecord > WhereRecord ;
if ( Where ( FileName , WhereRecord , AbortEvent , Log ) )
{
OutLocalFileName = FUtility : : GetPathWithCorrectCase ( * WhereRecord - > LocalPath ) ;
return true ;
}
else
{
OutLocalFileName = TEXT ( " " ) ;
return false ;
}
}
bool FPerforceConnection : : FindStreams ( const FString & Filter , TArray < FString > & OutStreamNames , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TArray < FString > Lines ;
if ( ! RunCommand ( FString : : Printf ( TEXT ( " streams -F \" Stream=%s \" " ) , * Filter ) , Lines , ECommandOptions : : None , AbortEvent , Log ) )
{
OutStreamNames . Empty ( ) ;
return false ;
}
TArray < FString > StreamNames ;
for ( const FString & Line : Lines )
{
TArray < FString > Tokens = Split ( Line , TEXT ( " " ) , true ) ;
if ( Tokens . Num ( ) < 2 | | Tokens [ 0 ] ! = TEXT ( " Stream " ) | | ! Tokens [ 1 ] . StartsWith ( TEXT ( " // " ) ) )
{
Log . Logf ( TEXT ( " Unexpected output from stream query: %s " ) , * Line ) ;
OutStreamNames . Empty ( ) ;
return false ;
}
StreamNames . Add ( Tokens [ 1 ] ) ;
}
OutStreamNames = StreamNames ;
return true ;
}
bool FPerforceConnection : : HasOpenFiles ( FEvent * AbortEvent , FOutputDevice & Log ) const
{
TArray < FPerforceFileRecord > Records ;
bool bResult = RunCommand ( TEXT ( " opened -m 1 " ) , Records , ECommandOptions : : None , AbortEvent , Log ) ;
return bResult & & Records . Num ( ) > 0 ;
}
bool FPerforceConnection : : SwitchStream ( const FString & NewStream , FEvent * AbortEvent , FOutputDevice & Log ) const
{
return RunCommand ( FString : : Printf ( TEXT ( " client -f -s -S \" %s \" \" %s \" " ) , * NewStream , * ClientName ) , ECommandOptions : : None , AbortEvent , Log ) ;
}
bool FPerforceConnection : : Describe ( int ChangeNumber , TSharedPtr < FPerforceDescribeRecord > & OutRecord , FEvent * AbortEvent , FOutputDevice & Log ) const
{
FString CommandLine = FString : : Printf ( TEXT ( " describe -s %d " ) , ChangeNumber ) ;
TArray < TMap < FString , FString > > Records ;
if ( ! RunCommandWithBinaryOutput ( CommandLine , Records , ECommandOptions : : None , AbortEvent , Log ) )
{
OutRecord = nullptr ;
return false ;
}
if ( Records . Num ( ) ! = 1 )
{
Log . Logf ( TEXT ( " Expected 1 record from p4 %s, got %d " ) , * CommandLine , Records . Num ( ) ) ;
OutRecord = nullptr ;
return false ;
}
FString Code ;
if ( ! TryGetValue ( Records [ 0 ] , TEXT ( " code " ) , Code ) | | Code ! = " stat " )
{
Log . Logf ( TEXT ( " Unexpected response from p4 %s (code=%s) " ) , * CommandLine , * Code ) ;
OutRecord = nullptr ;
return false ;
}
OutRecord = MakeShared < FPerforceDescribeRecord > ( Records [ 0 ] ) ;
return true ;
}
bool FPerforceConnection : : Where ( const FString & Filter , TSharedPtr < FPerforceWhereRecord > & OutWhereRecord , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TArray < FPerforceFileRecord > FileRecords ;
if ( ! RunCommand ( FString : : Printf ( TEXT ( " where \" %s \" " ) , * Filter ) , FileRecords , ECommandOptions : : None , AbortEvent , Log ) )
{
OutWhereRecord = nullptr ;
return false ;
}
FileRecords . RemoveAll ( [ ] ( const FPerforceFileRecord & Record ) { return Record . Unmap ; } ) ;
if ( FileRecords . Num ( ) = = 0 )
{
Log . Logf ( TEXT ( " '%s' is not mapped to workspace. " ) , * Filter ) ;
OutWhereRecord = nullptr ;
return false ;
}
else if ( FileRecords . Num ( ) > 1 )
{
Log . Logf ( TEXT ( " File is mapped to %d locations: " ) , FileRecords . Num ( ) ) ;
for ( const FPerforceFileRecord & FileRecord : FileRecords )
{
Log . Logf ( TEXT ( " %s " ) , * FileRecord . Path ) ;
}
OutWhereRecord = nullptr ;
return false ;
}
OutWhereRecord = MakeShared < FPerforceWhereRecord > ( ) ;
OutWhereRecord - > LocalPath = FileRecords [ 0 ] . Path ;
OutWhereRecord - > DepotPath = FileRecords [ 0 ] . DepotPath ;
OutWhereRecord - > ClientPath = FileRecords [ 0 ] . ClientPath ;
return true ;
}
bool FPerforceConnection : : Have ( const FString & Filter , TArray < FPerforceFileRecord > & OutFileRecords , FEvent * AbortEvent , FOutputDevice & Log ) const
{
return RunCommand ( FString : : Printf ( TEXT ( " have \" %s \" " ) , * Filter ) , OutFileRecords , ECommandOptions : : None , AbortEvent , Log ) ;
}
bool FPerforceConnection : : Stat ( const FString & Filter , TArray < FPerforceFileRecord > & OutFileRecords , FEvent * AbortEvent , FOutputDevice & Log ) const
{
return RunCommand ( FString : : Printf ( TEXT ( " fstat \" %s \" " ) , * Filter ) , OutFileRecords , ECommandOptions : : None , AbortEvent , Log ) ;
}
bool FPerforceConnection : : Sync ( const FString & Filter , FEvent * AbortEvent , FOutputDevice & Log ) const
{
return RunCommand ( FString ( TEXT ( " sync " ) ) + Filter , ECommandOptions : : IgnoreFilesUpToDateError , AbortEvent , Log ) ;
}
bool FPerforceConnection : : Sync ( const TArray < FString > & DepotPaths , int ChangeNumber , TFunction < void ( const FPerforceFileRecord & ) > SyncOutput , TArray < FString > & OutTamperedFiles , const FPerforceSyncOptions * Options , FEvent * AbortEvent , FOutputDevice & Log ) const
{
// Write all the files we want to sync to a temp file
TArray < FString > SyncFiles ;
for ( const FString & DepotPath : DepotPaths )
{
SyncFiles . Add ( FString : : Printf ( TEXT ( " %s@%d " ) , * DepotPath , ChangeNumber ) ) ;
}
FString TempFileName = GetTempFileName ( ) ;
FFileHelper : : SaveStringArrayToFile ( SyncFiles , * TempFileName ) ;
// Create a filter to strip all the sync records
FPerforceTagRecordParser Parser ( [ SyncOutput ] ( const TMap < FString , FString > & Tags ) { SyncOutput ( FPerforceFileRecord ( Tags ) ) ; } ) ;
FString CommandLine ;
CommandLine + = FString : : Printf ( TEXT ( " -x \" %s \" -z tag " ) , * TempFileName ) ;
if ( Options ! = nullptr & & Options - > NumRetries > 0 )
{
CommandLine + = FString : : Printf ( TEXT ( " -r %d " ) , Options - > NumRetries ) ;
}
if ( Options ! = nullptr & & Options - > TcpBufferSize > 0 )
{
CommandLine + = FString : : Printf ( TEXT ( " -v net.tcpsize=%d " ) , Options - > TcpBufferSize ) ;
}
CommandLine + = TEXT ( " sync " ) ;
if ( Options ! = nullptr & & Options - > NumThreads > 1 )
{
CommandLine + = FString : : Printf ( TEXT ( " --parallel=threads=%d " ) , Options - > NumThreads ) ;
}
return RunCommand ( CommandLine , nullptr , [ & Parser , & OutTamperedFiles , & Log ] ( const FPerforceOutputLine & Line ) { return FilterSyncOutput ( Line , Parser , OutTamperedFiles , Log ) ; } , ECommandOptions : : NoFailOnErrors | ECommandOptions : : IgnoreFilesUpToDateError | ECommandOptions : : IgnoreExitCode , AbortEvent , Log ) ;
}
bool FPerforceConnection : : LatestChangeList ( int & OutChangeList , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TArray < TMap < FString , FString > > OutLatestChange ;
RunCommand ( TEXT ( " changes -m 1 " ) , OutLatestChange , ECommandOptions : : None , AbortEvent , Log ) ;
if ( OutLatestChange . Num ( ) = = 1 )
{
FString StringChangeList ;
if ( TryGetValue ( OutLatestChange [ 0 ] , TEXT ( " change " ) , StringChangeList ) )
{
return FUtility : : TryParse ( * StringChangeList , OutChangeList ) ;
}
}
return false ;
}
bool FPerforceConnection : : FilterSyncOutput ( const FPerforceOutputLine & Line , FPerforceTagRecordParser & Parser , TArray < FString > & OutTamperedFiles , FOutputDevice & Log )
{
if ( Line . Channel = = EPerforceOutputChannel : : TaggedInfo )
{
Parser . OutputLine ( Line . Text ) ;
return true ;
}
Log . Logf ( TEXT ( " %s " ) , * Line . Text ) ;
static const FString Prefix = TEXT ( " Can't clobber writable file " ) ;
if ( Line . Channel = = EPerforceOutputChannel : : Error & & Line . Text . StartsWith ( Prefix ) )
{
OutTamperedFiles . Add ( Line . Text . Mid ( Prefix . Len ( ) ) . TrimStartAndEnd ( ) ) ;
return true ;
}
return Line . Channel ! = EPerforceOutputChannel : : Error ;
}
void FPerforceConnection : : ParseTamperedFile ( const FString & Line , TArray < FString > & OutTamperedFiles )
{
static const FString Prefix = TEXT ( " Can't clobber writable file " ) ;
if ( Line . StartsWith ( Prefix ) )
{
OutTamperedFiles . Add ( Line . Mid ( Prefix . Len ( ) ) . TrimStartAndEnd ( ) ) ;
}
}
bool FPerforceConnection : : SyncPreview ( const FString & Filter , int ChangeNumber , bool bOnlyFilesInThisChange , TArray < FPerforceFileRecord > & OutFileRecords , FEvent * AbortEvent , FOutputDevice & Log ) const
{
return RunCommand ( FString : : Printf ( TEXT ( " sync -n %s@%s%d " ) , * Filter , bOnlyFilesInThisChange ? TEXT ( " = " ) : TEXT ( " " ) , ChangeNumber ) , OutFileRecords , ECommandOptions : : IgnoreFilesUpToDateError | ECommandOptions : : IgnoreNoSuchFilesError , AbortEvent , Log ) ;
}
bool FPerforceConnection : : ForceSync ( const FString & Filter , int ChangeNumber , FEvent * AbortEvent , FOutputDevice & Log ) const
{
return RunCommand ( FString : : Printf ( TEXT ( " sync -f \" %s \" @%d " ) , * Filter , ChangeNumber ) , ECommandOptions : : IgnoreFilesUpToDateError , AbortEvent , Log ) ;
}
bool FPerforceConnection : : GetOpenFiles ( const FString & Filter , TArray < FPerforceFileRecord > & OutFileRecords , FEvent * AbortEvent , FOutputDevice & Log ) const
{
return RunCommand ( FString : : Printf ( TEXT ( " opened \" %s \" " ) , * Filter ) , OutFileRecords , ECommandOptions : : None , AbortEvent , Log ) ;
}
bool FPerforceConnection : : GetUnresolvedFiles ( const FString & Filter , TArray < FPerforceFileRecord > & OutFileRecords , FEvent * AbortEvent , FOutputDevice & Log ) const
{
return RunCommand ( FString : : Printf ( TEXT ( " fstat -Ru \" %s \" " ) , * Filter ) , OutFileRecords , ECommandOptions : : IgnoreNoSuchFilesError | ECommandOptions : : IgnoreFilesNotOpenedOnThisClientError , AbortEvent , Log ) ;
}
bool FPerforceConnection : : AutoResolveFile ( const FString & File , FEvent * AbortEvent , FOutputDevice & Log ) const
{
return RunCommand ( FString : : Printf ( TEXT ( " resolve -am %s " ) , * File ) , ECommandOptions : : None , AbortEvent , Log ) ;
}
bool FPerforceConnection : : GetActiveStream ( FString & OutStreamName , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TSharedPtr < FPerforceSpec > ClientSpec ;
if ( TryGetClientSpec ( ClientName , ClientSpec , AbortEvent , Log ) )
{
OutStreamName = ClientSpec - > GetField ( TEXT ( " Stream " ) ) ;
return OutStreamName . Len ( ) > 0 ;
}
else
{
OutStreamName . Empty ( ) ;
return false ;
}
}
bool FPerforceConnection : : RunCommand ( const FString & CommandLine , TArray < FPerforceFileRecord > & OutFileRecords , ECommandOptions Options , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TArray < TMap < FString , FString > > TagRecords ;
if ( ! RunCommand ( CommandLine , TagRecords , Options , AbortEvent , Log ) )
{
OutFileRecords . Empty ( ) ;
return false ;
}
else
{
Algo : : Transform ( TagRecords , OutFileRecords , [ ] ( const TMap < FString , FString > & Tags ) - > FPerforceFileRecord { return FPerforceFileRecord ( Tags ) ; } ) ;
return true ;
}
}
bool FPerforceConnection : : RunCommand ( const FString & CommandLine , TArray < FPerforceClientRecord > & OutClientRecords , ECommandOptions Options , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TArray < TMap < FString , FString > > TagRecords ;
if ( ! RunCommand ( CommandLine , TagRecords , Options , AbortEvent , Log ) )
{
UE_LOG ( LogUGSCore , Warning , TEXT ( " RunCommand1 returned false " ) ) ;
return false ;
}
else
{
Algo : : Transform ( TagRecords , OutClientRecords , [ ] ( const TMap < FString , FString > & Tags ) - > FPerforceClientRecord { return FPerforceClientRecord ( Tags ) ; } ) ;
return true ;
}
}
bool FPerforceConnection : : RunCommand ( const FString & CommandLine , TArray < TMap < FString , FString > > & OutTagRecords , ECommandOptions Options , FEvent * AbortEvent , FOutputDevice & Log ) const
{
TArray < FString > Lines ;
if ( ! RunCommand ( FString ( TEXT ( " -ztag " ) ) + CommandLine , EPerforceOutputChannel : : TaggedInfo , Lines , Options , AbortEvent , Log ) )
//if(!RunCommand(FString(CommandLine, EPerforceOutputChannel::TaggedInfo, Lines, Options, AbortEvent, Log))
{
return false ;
}
FPerforceTagRecordParser Parser ( [ & OutTagRecords ] ( const TMap < FString , FString > & Tags ) { OutTagRecords . Add ( Tags ) ; } ) ;
for ( const FString & Line : Lines )
{
Parser . OutputLine ( Line ) ;
}
return true ;
}
2022-10-11 11:51:56 -04:00
bool FPerforceConnection : : RunCommand ( const FString & CommandLine , ECommandOptions Options , FEvent * AbortEvent , FOutputDevice & Log , const FString & WritePipeText ) const
2022-08-12 15:13:26 -04:00
{
TArray < FString > Lines ;
2022-10-11 11:51:56 -04:00
return RunCommand ( CommandLine , Lines , Options , AbortEvent , Log , WritePipeText ) ;
2022-08-12 15:13:26 -04:00
}
2022-10-11 11:51:56 -04:00
bool FPerforceConnection : : RunCommand ( const FString & CommandLine , TArray < FString > & OutLines , ECommandOptions Options , FEvent * AbortEvent , FOutputDevice & Log , const FString & WritePipeText ) const
2022-08-12 15:13:26 -04:00
{
2022-10-11 11:51:56 -04:00
return RunCommand ( CommandLine , EPerforceOutputChannel : : Info , OutLines , Options , AbortEvent , Log , WritePipeText ) ;
2022-08-12 15:13:26 -04:00
}
2022-10-11 11:51:56 -04:00
bool FPerforceConnection : : RunCommand ( const FString & CommandLine , EPerforceOutputChannel Channel , TArray < FString > & OutLines , ECommandOptions Options , FEvent * AbortEvent , FOutputDevice & Log , const FString & WritePipeText ) const
2022-08-12 15:13:26 -04:00
{
FString FullCommandLine = GetFullCommandLine ( CommandLine , Options ) ;
Log . Logf ( TEXT ( " p4> %s %s " ) , * FPaths : : GetCleanFilename ( GPerforceExe . GetPerforceExe ( ) ) , * FullCommandLine ) ;
TArray < FString > RawOutputLines ;
2022-10-11 11:51:56 -04:00
int ExitCode = GPerforceExe . RunCommand ( FullCommandLine , RawOutputLines , AbortEvent , WritePipeText ) ;
2022-08-12 15:13:26 -04:00
if ( ExitCode ! = 0 & & ! EnumHasAnyFlags ( Options , ECommandOptions : : IgnoreExitCode ) )
{
return false ;
}
bool bResult = true ;
if ( EnumHasAnyFlags ( Options , ECommandOptions : : NoChannels ) )
{
OutLines = RawOutputLines ;
}
else
{
TArray < FString > LocalLines ;
for ( const FString & RawOutputLine : RawOutputLines )
{
bResult & = ParseCommandOutput ( RawOutputLine , [ & LocalLines , Channel , & Log ] ( const FPerforceOutputLine & Line ) { if ( Line . Channel = = Channel ) { LocalLines . Add ( Line . Text ) ; return true ; } else { Log . Logf ( TEXT ( " %s " ) , * Line . Text ) ; return Line . Channel ! = EPerforceOutputChannel : : Error ; } } , Options ) ;
}
OutLines = LocalLines ;
}
return bResult ;
}
bool FPerforceConnection : : RunCommand ( const FString & CommandLine , const TCHAR * Input , TFunction < bool ( const FPerforceOutputLine & ) > HandleOutput , ECommandOptions Options , FEvent * AbortEvent , FOutputDevice & Log ) const
{
FString FullCommandLine = GetFullCommandLine ( CommandLine , Options ) ;
Log . Logf ( TEXT ( " p4> %s %s " ) , * FPaths : : GetCleanFilename ( GPerforceExe . GetPerforceExe ( ) ) , * FullCommandLine ) ;
TArray < FString > RawOutputLines ;
2022-08-16 18:20:22 -04:00
int ExitCode = GPerforceExe . RunCommand ( FullCommandLine , RawOutputLines , AbortEvent ) ;
2022-08-12 15:13:26 -04:00
// TODO check this vs the old way as things *may* have changed. Before they were handling each line as it was coming
// back from the process. here we just spin + collect all the output. Then try to process all the output, then if the
// exit code was not good return false still.
bool bResult = true ;
FString Line ;
for ( const FString & RawOutputLine : RawOutputLines )
{
bResult & = ParseCommandOutput ( Line , HandleOutput , Options ) ;
}
if ( ExitCode ! = 0 & & ! EnumHasAnyFlags ( Options , ECommandOptions : : IgnoreExitCode ) )
{
bResult = false ;
}
return bResult ;
}
FString FPerforceConnection : : GetFullCommandLine ( const FString & CommandLine , ECommandOptions Options ) const
{
FString FullCommandLine ;
if ( ServerAndPort . Len ( ) > 0 )
{
FullCommandLine + = FString : : Printf ( TEXT ( " -p%s " ) , * ServerAndPort ) ;
}
if ( UserName . Len ( ) > 0 )
{
FullCommandLine + = FString : : Printf ( TEXT ( " -u%s " ) , * UserName ) ;
}
if ( ( Options & ECommandOptions : : NoClient ) = = ECommandOptions : : None & & ClientName . Len ( ) > 0 )
{
FullCommandLine + = FString : : Printf ( TEXT ( " -c%s " ) , * ClientName ) ;
}
if ( ( Options & ECommandOptions : : NoChannels ) = = ECommandOptions : : None )
{
FullCommandLine + = TEXT ( " -s " ) ;
}
FullCommandLine + = CommandLine ;
return FullCommandLine ;
}
bool FPerforceConnection : : ParseCommandOutput ( const FString & Text , TFunction < bool ( const FPerforceOutputLine & ) > HandleOutput , ECommandOptions Options ) const
{
if ( EnumHasAnyFlags ( Options , ECommandOptions : : NoChannels ) )
{
FPerforceOutputLine Line ( EPerforceOutputChannel : : Unknown , Text ) ;
return HandleOutput ( Line ) ;
}
else if ( ! IgnoreCommandOutput ( Text , Options ) )
{
FPerforceOutputLine Line ;
if ( Text . StartsWith ( TEXT ( " text: " ) ) )
{
Line = FPerforceOutputLine ( EPerforceOutputChannel : : Text , Text . Mid ( 6 ) ) ;
}
else if ( Text . StartsWith ( " info: " ) )
{
Line = FPerforceOutputLine ( EPerforceOutputChannel : : Info , Text . Mid ( 6 ) ) ;
}
else if ( Text . StartsWith ( " info1: " ) )
{
Line = FPerforceOutputLine ( IsValidTag ( Text , 7 ) ? EPerforceOutputChannel : : TaggedInfo : EPerforceOutputChannel : : Info , Text . Mid ( 7 ) ) ;
}
else if ( Text . StartsWith ( " warning: " ) )
{
Line = FPerforceOutputLine ( EPerforceOutputChannel : : Warning , Text . Mid ( 9 ) ) ;
}
else if ( Text . StartsWith ( " error: " ) )
{
Line = FPerforceOutputLine ( EPerforceOutputChannel : : Error , Text . Mid ( 7 ) ) ;
}
else
{
Line = FPerforceOutputLine ( EPerforceOutputChannel : : Unknown , Text ) ;
}
return HandleOutput ( Line ) & & ( Line . Channel ! = EPerforceOutputChannel : : Error | | EnumHasAnyFlags ( Options , ECommandOptions : : NoFailOnErrors ) ) & & Line . Channel ! = EPerforceOutputChannel : : Unknown ;
}
return true ;
}
bool FPerforceConnection : : IsValidTag ( const FString & Line , int StartIndex )
{
// Annoyingly, we sometimes get commentary with an info1: prefix. Since it typically starts with a depot or file path, we can pick it out.
for ( int Idx = StartIndex ; Idx < Line . Len ( ) & & Line [ Idx ] ! = ' ' ; Idx + + )
{
if ( Line [ Idx ] = = ' / ' | | Line [ Idx ] = = ' \\ ' )
{
return false ;
}
}
return true ;
}
bool FPerforceConnection : : IgnoreCommandOutput ( const FString & Text , ECommandOptions Options )
{
if ( Text . StartsWith ( TEXT ( " exit: " ) ) | | Text . StartsWith ( TEXT ( " info2: " ) ) | | Text . Len ( ) = = 0 )
{
return true ;
}
else if ( EnumHasAnyFlags ( Options , ECommandOptions : : IgnoreFilesUpToDateError ) & & Text . StartsWith ( TEXT ( " error: " ) ) & & Text . EndsWith ( TEXT ( " - file(s) up-to-date. " ) ) )
{
return true ;
}
else if ( EnumHasAnyFlags ( Options , ECommandOptions : : IgnoreNoSuchFilesError ) & & Text . StartsWith ( TEXT ( " error: " ) ) & & Text . EndsWith ( TEXT ( " - no such file(s). " ) ) )
{
return true ;
}
else if ( EnumHasAnyFlags ( Options , ECommandOptions : : IgnoreFilesNotInClientViewError ) & & Text . StartsWith ( TEXT ( " error: " ) ) & & Text . EndsWith ( TEXT ( " - file(s) not in client view. " ) ) )
{
return true ;
}
else if ( EnumHasAnyFlags ( Options , ECommandOptions : : IgnoreFilesNotOpenedOnThisClientError ) & & Text . StartsWith ( TEXT ( " error: " ) ) & & Text . EndsWith ( TEXT ( " - file(s) not opened on this client. " ) ) )
{
return true ;
}
return false ;
}
const uint8 * FPerforceConnection : : ReadBinaryField ( const uint8 * Pos , const uint8 * End , FString * OutField )
{
// Read the field type
if ( End - Pos < sizeof ( uint8 ) )
{
return nullptr ;
}
uint8 KeyFieldType = * Pos ;
Pos + + ;
// Skip over the field data
if ( KeyFieldType = = ' s ' )
{
// Read the string length
if ( End - Pos < sizeof ( uint32 ) )
{
return nullptr ;
}
uint32 KeyLength ;
memcpy ( & KeyLength , Pos , sizeof ( uint32 ) ) ;
Pos + = sizeof ( uint32 ) ;
// Read the string data
if ( End - Pos < KeyLength )
{
return nullptr ;
}
if ( OutField ! = nullptr )
{
TStringConversion < FUTF8ToTCHAR_Convert > Converter ( ( const ANSICHAR * ) Pos , KeyLength ) ;
* OutField = FString ( Converter . Length ( ) , Converter . Get ( ) ) ;
}
Pos + = KeyLength ;
return Pos ;
}
else if ( KeyFieldType = = ' i ' )
{
// Skip over the integer data
if ( End - Pos < sizeof ( uint32 ) )
{
return nullptr ;
}
if ( OutField ! = nullptr )
{
int32 Value ;
memcpy ( & Value , Pos , sizeof ( int32 ) ) ;
* OutField = FString : : Printf ( TEXT ( " %d " ) , Value ) ;
}
Pos + = sizeof ( uint32 ) ;
return Pos ;
}
else
{
checkf ( false , TEXT ( " Invalid field type '%d' (%c) " ) , ( int ) * Pos , ( TCHAR ) * Pos ) ;
return nullptr ;
}
}
const uint8 * FPerforceConnection : : ReadBinaryRecord ( const uint8 * Pos , const uint8 * End , TMap < FString , FString > * OutRecord )
{
if ( End > Pos & & * Pos = = ' { ' )
{
Pos + + ;
while ( Pos < End )
{
// If this is the end of the dictionary, return success
if ( * Pos = = ' 0 ' )
{
return Pos + 1 ;
}
// Read or skip over the field data
if ( OutRecord = = nullptr )
{
Pos = ReadBinaryField ( Pos , End , nullptr ) ;
if ( Pos = = nullptr )
{
return nullptr ;
}
Pos = ReadBinaryField ( Pos , End , nullptr ) ;
if ( Pos = = nullptr )
{
return nullptr ;
}
}
else
{
FString Key ;
Pos = ReadBinaryField ( Pos , End , & Key ) ;
if ( Pos = = nullptr )
{
return nullptr ;
}
FString Value ;
Pos = ReadBinaryField ( Pos , End , & Value ) ;
if ( Pos = = nullptr )
{
return nullptr ;
}
OutRecord - > FindOrAdd ( MoveTemp ( Key ) ) = MoveTemp ( Value ) ;
}
}
}
return nullptr ;
}
/// <summary>
/// Execute a Perforce command and parse the output as marshalled Python objects. This is more robustly defined than the text-based tagged output
/// format, because it avoids ambiguity when returned fields can have newlines.
/// </summary>
/// <param name="CommandLine">Command line to execute Perforce with</param>
/// <param name="TaggedOutput">List that receives the output records</param>
/// <param name="WithClient">Whether to include client information on the command line</param>
bool FPerforceConnection : : RunCommandWithBinaryOutput ( const FString & CommandLine , TArray < TMap < FString , FString > > & OutRecords , ECommandOptions Options , FEvent * AbortEvent , FOutputDevice & Log ) const
{
return RunCommandWithBinaryOutput ( CommandLine , [ & OutRecords ] ( const TMap < FString , FString > & Record ) { OutRecords . Add ( Record ) ; return true ; } , Options , AbortEvent , Log ) ;
}
/// <summary>
/// Execute a Perforce command and parse the output as marshalled Python objects. This is more robustly defined than the text-based tagged output
/// format, because it avoids ambiguity when returned fields can have newlines.
/// </summary>
/// <param name="CommandLine">Command line to execute Perforce with</param>
/// <param name="TaggedOutput">List that receives the output records</param>
/// <param name="WithClient">Whether to include client information on the command line</param>
bool FPerforceConnection : : RunCommandWithBinaryOutput ( const FString & CommandLine , TFunction < void ( const TMap < FString , FString > & ) > HandleOutput , ECommandOptions Options , FEvent * AbortEvent , FOutputDevice & Log ) const
{
FString FullCommandLine ( TEXT ( " -G " ) + CommandLine ) ;
uint32 ProcId ;
2022-10-11 11:51:56 -04:00
void * ChildWritePipe = nullptr ;
void * ParentReadPipe = nullptr ;
FPlatformProcess : : CreatePipe ( ParentReadPipe , ChildWritePipe ) ;
2022-08-12 15:13:26 -04:00
2022-10-11 11:51:56 -04:00
FProcHandle P4Proc = FPlatformProcess : : CreateProc ( * GPerforceExe . GetPerforceExe ( ) , * FullCommandLine , false , true , true , & ProcId , 0 , nullptr , ChildWritePipe ) ;
2022-08-12 15:13:26 -04:00
size_t BufferPos = 0 ;
size_t BufferEnd = 0 ;
TArray < uint8 > Buffer ;
TMap < FString , FString > Record ;
for ( ; ; )
{
// Determine if the process has exited. Do this before reading, so we'll only exit the loop if no data is read AND the process has exited.
bool bHasExited = ! FPlatformProcess : : IsProcRunning ( P4Proc ) ;
// Check that we don't have an abort event
if ( AbortEvent - > Wait ( FTimespan : : Zero ( ) ) )
{
2022-08-16 18:20:22 -04:00
FPlatformProcess : : TerminateProc ( P4Proc ) ;
2022-08-12 15:13:26 -04:00
return false ;
//throw FAbortException();
}
// Read data from the child process
TArray < uint8 > TempBuffer ;
2022-10-11 11:51:56 -04:00
bool bRead = FPlatformProcess : : ReadPipeToArray ( ParentReadPipe , TempBuffer ) ;
2022-08-12 15:13:26 -04:00
size_t BytesRead = TempBuffer . Num ( ) ;
// Add new data to the end of the Buffer
Buffer + = TempBuffer ;
if ( BytesRead = = 0 )
{
// If it exited, quit. Otherwise sleep until data is available.
if ( bHasExited )
{
break ;
}
else if ( AbortEvent - > Wait ( FTimespan : : FromMilliseconds ( 50 ) ) )
{
2022-08-16 18:20:22 -04:00
FPlatformProcess : : TerminateProc ( P4Proc ) ;
2022-08-12 15:13:26 -04:00
return false ;
//throw FAbortException();
}
}
else
{
// Add the read bytes to the buffer
BufferEnd + = BytesRead ;
// Process all the records in the buffer that we can
const uint8 * RecordPos = Buffer . GetData ( ) + BufferPos ;
const uint8 * RecordMaxPos = Buffer . GetData ( ) + BufferEnd ;
for ( ; ; )
{
const uint8 * RecordEnd = ReadBinaryRecord ( RecordPos , RecordMaxPos , & Record ) ;
if ( RecordEnd = = nullptr )
{
break ;
}
HandleOutput ( Record ) ;
RecordPos = RecordEnd ;
}
BufferPos = RecordPos - Buffer . GetData ( ) ;
// Shrink the buffer down if we've got spare space
if ( BufferPos > 64 )
{
BufferEnd - = BufferPos ;
memmove ( Buffer . GetData ( ) , Buffer . GetData ( ) + BufferPos , BufferEnd ) ;
BufferPos = 0 ;
}
}
}
2022-10-11 11:51:56 -04:00
FPlatformProcess : : ClosePipe ( ParentReadPipe , ChildWritePipe ) ;
2022-08-12 15:13:26 -04:00
int ExitCode = - 1 ;
checkf ( BufferPos = = BufferEnd , TEXT ( " %d bytes of incomplete record data received from P4 " ) , BufferEnd - BufferPos ) ;
FPlatformProcess : : GetProcReturnCode ( P4Proc , & ExitCode ) ;
return ExitCode = = 0 ;
}
void FPerforceConnection : : OpenP4V ( const FString & AdditionalArgs ) const
{
# if PLATFORM_WINDOWS
const TCHAR * P4VExe = TEXT ( " p4v.exe " ) ;
# else
const TCHAR * P4VExe = TEXT ( " p4v " ) ;
# endif
FString P4VArgs = GetArgumentsForExternalProgram ( ) + TEXT ( " " ) + AdditionalArgs ;
FPlatformProcess : : CreateProc ( P4VExe , * P4VArgs , true , true , true , nullptr , 0 , nullptr , nullptr ) ;
}
void FPerforceConnection : : OpenP4VC ( const FString & AdditionalArgs ) const
{
# if PLATFORM_WINDOWS
const TCHAR * P4VCExe = TEXT ( " p4vc.exe " ) ;
# else
const TCHAR * P4VCExe = TEXT ( " p4vc " ) ;
# endif
FString P4VCArgs = GetArgumentsForExternalProgram ( ) + TEXT ( " " ) + AdditionalArgs ;
FPlatformProcess : : CreateProc ( P4VCExe , * P4VCArgs , true , true , true , nullptr , 0 , nullptr , nullptr ) ;
}
FString FPerforceConnection : : GetArgumentsForExternalProgram ( bool bIncludeClient ) const
{
FString ExternalArgs = FString : : Printf ( TEXT ( " -p \" %s \" -u \" %s \" " ) , * ServerAndPort , * UserName ) ;
if ( bIncludeClient & & ! ClientName . IsEmpty ( ) )
{
ExternalArgs + = FString : : Printf ( TEXT ( " -c \" %s \" " ) , * ClientName ) ;
}
return ExternalArgs ;
}
//// FPerforceUtils ////
FString FPerforceUtils : : GetClientOrDepotDirectoryName ( const TCHAR * ClientFile )
{
const TCHAR * LastSlash = FCString : : Strrchr ( ClientFile , ' / ' ) ;
if ( LastSlash = = nullptr )
{
return " " ;
}
else
{
return FString ( LastSlash - ClientFile , ClientFile ) ;
}
}
bool FPerforceUtils : : TryParseDateTime ( const FString & Date , const FString & Time , FDateTime & OutDate )
{
// Parse the date
const TCHAR * Pos = * Date ;
TCHAR * End = nullptr ;
int32 Year = FCString : : Strtoi ( Pos , & End , 10 ) ;
if ( End < = Pos | | * End ! = ' / ' )
{
return false ;
}
Pos = End + 1 ;
int32 Month = FCString : : Strtoi ( Pos , & End , 10 ) ;
if ( End < = Pos | | * End ! = ' / ' )
{
return false ;
}
Pos = End + 1 ;
int32 Day = FCString : : Strtoi ( Pos , & End , 10 ) ;
if ( End < = Pos | | * End ! = 0 )
{
return false ;
}
// Parse the time
Pos = * Time ;
int Hour = FCString : : Strtoi ( Pos , & End , 10 ) ;
if ( End < = Pos | | * End ! = ' : ' )
{
return false ;
}
Pos = End + 1 ;
int Minute = FCString : : Strtoi ( Pos , & End , 10 ) ;
if ( End < = Pos | | * End ! = ' : ' )
{
return false ;
}
Pos = End + 1 ;
int Second = FCString : : Strtoi ( Pos , & End , 10 ) ;
if ( End < = Pos | | * End ! = 0 )
{
return false ;
}
OutDate = FDateTime ( Year , Month , Day , Hour , Minute , Second ) ;
return true ;
}
} // namespace UGSCore