259 lines
6.9 KiB
C++
259 lines
6.9 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Utility.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "HAL/PlatformFileManager.h"
|
|
#include "HAL/PlatformProcess.h"
|
|
#include "Misc/Paths.h"
|
|
#include "HAL/Event.h"
|
|
|
|
DEFINE_LOG_CATEGORY(LogUGSCore);
|
|
|
|
namespace UGSCore
|
|
{
|
|
|
|
bool FUtility::TryParse(const TCHAR* Text, int32& OutValue)
|
|
{
|
|
TCHAR* TextEnd;
|
|
OutValue = FCString::Strtoi(Text, &TextEnd, 10);
|
|
return (TextEnd != Text && *TextEnd == 0);
|
|
}
|
|
|
|
bool FUtility::TryParse(const TCHAR* Text, int64& OutValue)
|
|
{
|
|
TCHAR* TextEnd;
|
|
OutValue = FCString::Strtoi64(Text, &TextEnd, 10);
|
|
return (TextEnd != Text && *TextEnd == 0);
|
|
}
|
|
|
|
bool FUtility::IsFileUnderDirectory(const TCHAR* FileName, const TCHAR* DirectoryName)
|
|
{
|
|
FString FullDirectoryName = FPaths::ConvertRelativePathToFull(DirectoryName);
|
|
FPaths::MakePlatformFilename(FullDirectoryName);
|
|
|
|
if(!FullDirectoryName.EndsWith(FPlatformMisc::GetDefaultPathSeparator()))
|
|
{
|
|
FullDirectoryName += FPlatformMisc::GetDefaultPathSeparator();
|
|
}
|
|
|
|
FString FullFileName = FPaths::ConvertRelativePathToFull(FileName);
|
|
FPaths::MakePlatformFilename(FullFileName);
|
|
|
|
return FullFileName.StartsWith(FullDirectoryName);
|
|
}
|
|
|
|
FString FUtility::GetPathWithCorrectCase(const FString& Path)
|
|
{
|
|
// Visitor which corrects the case of a filename against any matching item in a directory
|
|
struct FFixCaseVisitor : public IPlatformFile::FDirectoryVisitor
|
|
{
|
|
FString Name;
|
|
|
|
FFixCaseVisitor(FString InName) : Name(InName)
|
|
{
|
|
}
|
|
|
|
virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override
|
|
{
|
|
if (Name == FilenameOrDirectory)
|
|
{
|
|
Name = FilenameOrDirectory;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
FString ParentDir = FPaths::GetPath(Path);
|
|
if(ParentDir.Len() == 0)
|
|
{
|
|
return Path;
|
|
}
|
|
#if PLATFORM_WINDOWS
|
|
else if(ParentDir.Len() == 2 && ParentDir[1] == ':')
|
|
{
|
|
ParentDir = ParentDir.ToUpper() + FPlatformMisc::GetDefaultPathSeparator();
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
ParentDir = GetPathWithCorrectCase(ParentDir) + FPlatformMisc::GetDefaultPathSeparator();
|
|
}
|
|
|
|
FFixCaseVisitor Visitor(ParentDir + FPaths::GetCleanFilename(Path));
|
|
IFileManager::Get().IterateDirectory(*ParentDir, Visitor);
|
|
return Visitor.Name;
|
|
}
|
|
|
|
FString FUtility::FormatUserName(const TCHAR* UserName)
|
|
{
|
|
FString NormalUserName;
|
|
for(int Idx = 0; UserName[Idx] != 0; Idx++)
|
|
{
|
|
if(Idx == 0 || UserName[Idx - 1] == '.')
|
|
{
|
|
NormalUserName += FChar::ToUpper(UserName[Idx]);
|
|
}
|
|
else if(UserName[Idx] == '.')
|
|
{
|
|
NormalUserName += ' ';
|
|
}
|
|
else
|
|
{
|
|
NormalUserName += UserName[Idx];
|
|
}
|
|
}
|
|
return NormalUserName;
|
|
}
|
|
|
|
int FUtility::ExecuteProcess(const TCHAR* FileName, const TCHAR* CommandLine, const TCHAR* Input, FEvent* AbortEvent, FLineBasedTextWriter& Log)
|
|
{
|
|
return ExecuteProcess(FileName, CommandLine, Input, AbortEvent, [&Log](const FString& Line){ Log.WriteLine(Line); });
|
|
}
|
|
|
|
int FUtility::ExecuteProcess(const TCHAR* FileName, const TCHAR* CommandLine, const TCHAR* Input, FEvent* AbortEvent, TArray<FString>& OutLines)
|
|
{
|
|
uint32 ProcId;
|
|
void* ReadPipe = nullptr;
|
|
void* WritePipe = nullptr;
|
|
FPlatformProcess::CreatePipe(ReadPipe, WritePipe);
|
|
|
|
FProcHandle Proc = FPlatformProcess::CreateProc(FileName, CommandLine, false, true, true, &ProcId, 0, nullptr, WritePipe, ReadPipe);
|
|
|
|
FString Output;
|
|
FString LatestOutput = FPlatformProcess::ReadPipe(ReadPipe);
|
|
while (FPlatformProcess::IsProcRunning(Proc) || !LatestOutput.IsEmpty())
|
|
{
|
|
Output += LatestOutput;
|
|
LatestOutput = FPlatformProcess::ReadPipe(ReadPipe);
|
|
|
|
if (AbortEvent->Wait(FTimespan::Zero()))
|
|
{
|
|
FPlatformProcess::TerminateProc(Proc);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
FPlatformProcess::ClosePipe(ReadPipe, WritePipe);
|
|
|
|
// TODO worried this may not always work for say p4 servers sending out only \n on Windows host
|
|
Output.ParseIntoArray(OutLines, LINE_TERMINATOR);
|
|
|
|
int ExitCode = -1;
|
|
bool GotReturnCode = FPlatformProcess::GetProcReturnCode(Proc, &ExitCode);
|
|
|
|
if (GotReturnCode)
|
|
{
|
|
return ExitCode;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int FUtility::ExecuteProcess(const TCHAR* FileName, const TCHAR* CommandLine, const TCHAR* Input, FEvent* AbortEvent, const TFunction<void(const FString&)>& OutputLine)
|
|
{
|
|
uint32 ProcId;
|
|
void* ReadPipe = nullptr;
|
|
void* WritePipe = nullptr;
|
|
FPlatformProcess::CreatePipe(ReadPipe, WritePipe);
|
|
|
|
FProcHandle Proc = FPlatformProcess::CreateProc(FileName, CommandLine, false, true, true, &ProcId, 0, nullptr, WritePipe, ReadPipe);
|
|
|
|
FString LatestOutput = FPlatformProcess::ReadPipe(ReadPipe);
|
|
while (FPlatformProcess::IsProcRunning(Proc) || !LatestOutput.IsEmpty())
|
|
{
|
|
// Check if we end up having a perfect data blob, with a perfect amount of lines
|
|
bool bEndsWithLineTerminator = LatestOutput.EndsWith(LINE_TERMINATOR);
|
|
|
|
// Parsing into this array will give us full lines, minus the last entry will be the left over data
|
|
TArray<FString> Lines;
|
|
// TODO ... worried this LINE_TERMINATOR *may* not always be correct urg. For example p4 is on a Linux server, and on Windows im not sure if it will return \r\n
|
|
LatestOutput.ParseIntoArray(Lines, LINE_TERMINATOR);
|
|
|
|
// If there is no data, we cannot, if theres only 1 entry or we dont have any lines found in the data chunk, move all over to the LeftOverLine
|
|
FString LeftOverLine;
|
|
|
|
// If we have more then 2 lines, or we only have 1 line and it didnt contain *a* LINE_TERMINATOR its left over data
|
|
if (Lines.Num() > 1 || (Lines.Num() > 0 && !bEndsWithLineTerminator))
|
|
{
|
|
LeftOverLine = Lines.Pop();
|
|
}
|
|
|
|
for (const FString& Line : Lines)
|
|
{
|
|
OutputLine(Line);
|
|
}
|
|
|
|
LatestOutput = LeftOverLine + FPlatformProcess::ReadPipe(ReadPipe);
|
|
|
|
if (AbortEvent->Wait(FTimespan::Zero()))
|
|
{
|
|
FPlatformProcess::TerminateProc(Proc);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
FPlatformProcess::ClosePipe(ReadPipe, WritePipe);
|
|
|
|
int ExitCode = -1;
|
|
bool GotReturnCode = FPlatformProcess::GetProcReturnCode(Proc, &ExitCode);
|
|
|
|
if (GotReturnCode)
|
|
{
|
|
return ExitCode;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
FString FUtility::ExpandVariables(const TCHAR* InputString, const TMap<FString, FString>* AdditionalVariables)
|
|
{
|
|
FString Result = InputString;
|
|
for(int Idx = 0;;)
|
|
{
|
|
Idx = Result.Find(TEXT("$("), ESearchCase::CaseSensitive, ESearchDir::FromStart, Idx);
|
|
if(Idx == INDEX_NONE)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Find the end of the variable name
|
|
int EndIdx = Result.Find(TEXT(")"), ESearchCase::CaseSensitive, ESearchDir::FromStart, Idx + 2);
|
|
if (EndIdx == INDEX_NONE)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Extract the variable name from the string
|
|
FString Name = Result.Mid(Idx + 2, EndIdx - (Idx + 2));
|
|
|
|
// Find the value for it, either from the dictionary or the environment block
|
|
const FString* VariableValue = nullptr;
|
|
if(AdditionalVariables != nullptr)
|
|
{
|
|
VariableValue = AdditionalVariables->Find(Name);
|
|
}
|
|
|
|
FString Value;
|
|
if(VariableValue != nullptr)
|
|
{
|
|
Value = *VariableValue;
|
|
}
|
|
else
|
|
{
|
|
Value = FPlatformMisc::GetEnvironmentVariable(*Name);
|
|
if(Value.IsEmpty())
|
|
{
|
|
Idx = EndIdx + 1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Replace the variable, or skip past it
|
|
Result = Result.Mid(0, Idx) + Value + Result.Mid(EndIdx + 1);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
} // namespace UGSCore
|