Files
UnrealEngine/Engine/Source/Programs/SubmitTool/Private/Logic/PreflightService.cpp
2025-05-18 13:04:45 +08:00

750 lines
26 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PreflightService.h"
#include "GenericPlatform/GenericPlatformProcess.h"
#include "HttpModule.h"
#include "Interfaces/IHttpResponse.h"
#include "JsonObjectConverter.h"
#include "Misc/Paths.h"
#include "Modules/ModuleManager.h"
#include "Serialization/JsonSerializer.h"
#include "Widgets/Docking/SDockTab.h"
#include "Internationalization/Regex.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Text/STextBlock.h"
#include "CommandLine/CmdLineParameters.h"
#include "Configuration/Configuration.h"
#include "Logic/Services/Interfaces/ISTSourceControlService.h"
#include "Logic/CredentialsService.h"
#include "Logic/TagService.h"
#include "Logging/SubmitToolLog.h"
#include "Models/ModelInterface.h"
#include "Models/SubmitToolUserPrefs.h"
#include "Telemetry/TelemetryService.h"
#include "Parameters/SubmitToolParameters.h"
FPreflightService::FPreflightService(
const FHordeParameters& InSettings,
FModelInterface* InModelInterface,
TWeakPtr<FSubmitToolServiceProvider> InServiceProvider)
:
Definition(InSettings),
ServiceProvider(InServiceProvider),
ModelInterface(InModelInterface),
State(EPreflightServiceState::Idle),
LastErrorMessage(TEXT(""))
{
TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FPreflightService::Tick));
DeleteShelveCallback = FSourceControlOperationComplete::CreateRaw(this, &FPreflightService::OnDeleteShelveOperationComplete);
CreateShelveCallback = FSourceControlOperationComplete::CreateRaw(this, &FPreflightService::OnCreateShelveOperationComplete);
PreflightTag = ServiceProvider.Pin()->GetService<FTagService>()->GetTagOfSubtype(TEXT("preflight"));
}
FPreflightService::~FPreflightService()
{
FTSTicker::GetCoreTicker().RemoveTicker(TickHandle);
OnPreflightDataUpdated.Clear();
}
bool FPreflightService::Tick(float DeltaTime)
{
TSharedPtr<FChangelistService> ChangelistService = ServiceProvider.Pin()->GetService<FChangelistService>();
switch(State)
{
//////////////////////////////////////////////////
case EPreflightServiceState::Idle:
// Do nothing, wait for someone to press the "Start" preflight button
break;
//////////////////////////////////////////////////
case EPreflightServiceState::RequestDeleteShelve:
if(ChangelistService->GetFilesInCL().Num() == 0)
{
LastErrorMessage = TEXT("Missing local files! We can't update the shelved files!");
State = EPreflightServiceState::Error;
}
else if(ChangelistService->HasShelvedFiles())
{
EDialogFactoryResult DialogResult = static_cast<EDialogFactoryResult>(ShowRecreateShelveDialog());
if(DialogResult == EDialogFactoryResult::FirstButton)
{
UE_LOG(LogSubmitTool, Log, TEXT("Preflight: Deleting shelved files"));
State = EPreflightServiceState::WaitingForDeleteShelve;
bCheckShelveInstead = false;
ChangelistService->DeleteShelvedFiles(DeleteShelveCallback);
}
else if(DialogResult == EDialogFactoryResult::SecondButton)
{
UE_LOG(LogSubmitTool, Log, TEXT("Preflight: Using existing shelved files"));
bCheckShelveInstead = true;
State = EPreflightServiceState::StartPreflight;
}
else
{
UE_LOG(LogSubmitTool, Log, TEXT("Preflight: Request cancelled"));
State = EPreflightServiceState::Idle;
}
}
else
{
State = EPreflightServiceState::RequestCreateShelve;
}
break;
//////////////////////////////////////////////////
case EPreflightServiceState::WaitingForDeleteShelve:
// Do nothing, wait for the delete shelve callback
break;
//////////////////////////////////////////////////
case EPreflightServiceState::RequestCreateShelve:
UE_LOG(LogSubmitTool, Log, TEXT("Preflight: Creating shelved files..."));
State = EPreflightServiceState::WaitingForCreateShelve;
ChangelistService->CreateShelvedFiles(CreateShelveCallback);
break;
//////////////////////////////////////////////////
case EPreflightServiceState::WaitingForCreateShelve:
// Do nothing, wait for the create shelve callback
break;
//////////////////////////////////////////////////
case EPreflightServiceState::StartPreflight:
StartPreflight();
break;
//////////////////////////////////////////////////
case EPreflightServiceState::Error:
UE_LOG(LogSubmitTool, Error, TEXT("Preflight: \"%s\""), *LastErrorMessage);
State = EPreflightServiceState::Idle;
break;
}
return true;
}
TMap<FString, FStringFormatArg> FPreflightService::GetFormatParameters() const
{
TMap<FString, FStringFormatArg> FormatMap =
{
{ TEXT("URL"), Definition.HordeServerAddress },
{ TEXT("CLID"), ServiceProvider.Pin()->GetService<FChangelistService>()->GetCLID()}
};
FormatMap.Add(TEXT("Stream"), StreamName);
FormatMap.Add(TEXT("Template"), FString());
FormatMap.Add(TEXT("AdditionalTasks"), FString());
FPreflightTemplateDefinition Template;
if(SelectPreflightTemplate(Template))
{
FormatMap[TEXT("Template")] = Template.Template;
FormatMap[TEXT("AdditionalTasks")] = GetAdditionalTasksString(Template);
}
else
{
FormatMap[TEXT("Template")] = Definition.DefaultPreflightTemplate;
}
return FormatMap;
}
void FPreflightService::RequestPreflight()
{
if(State == EPreflightServiceState::Idle)
{
if(!ServiceProvider.Pin()->GetService<ISTSourceControlService>()->GetClientStreams().IsEmpty())
{
UE_LOG(LogSubmitTool, Log, TEXT("Preflight: Requesting..."));
State = EPreflightServiceState::RequestDeleteShelve;
}
else
{
UE_LOG(LogSubmitTool, Error, TEXT("Couldn't retrieve stream name in this p4 client. Submit tool can't start a preflight, see previous errors."));
}
}
else
{
// Do nothing, we're already busy trying to start a preflight
}
}
void FPreflightService::QueueFetch(bool bRequeue, float InSeconds)
{
FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([this, bRequeue](float DeltaTime) { FetchPreflightInfo(bRequeue); return false; }), InSeconds);
}
void FPreflightService::Requeue()
{
float WaitTime = Definition.FetchPreflightEachSeconds;
if(HordePreflights.IsValid())
{
for(const FPreflightData& PFData : HordePreflights->PreflightList)
{
if(PFData.CachedResults.State != EPreflightState::Completed)
{
WaitTime = Definition.FetchPreflightEachSecondsWhenInProgress;
break;
}
}
}
for(const TPair<FString, FPreflightData>& Pair : UnlinkedHordePreflights)
{
if(Pair.Value.CachedResults.State != EPreflightState::Completed)
{
WaitTime = Definition.FetchPreflightEachSecondsWhenInProgress;
break;
}
}
QueueFetch(true, WaitTime);
}
void FPreflightService::FetchPreflightInfo(bool bRequeue, const FString& InOAuthToken)
{
TSharedPtr<FCredentialsService> CredentialsService = ServiceProvider.Pin()->GetService<FCredentialsService>();
if(Definition.HordeServerAddress.IsEmpty() || !CredentialsService->IsOIDCTokenEnabled() || FModelInterface::GetState() == ESubmitToolAppState::Finished)
{
return;
}
TSharedPtr<FChangelistService> ChangelistService = ServiceProvider.Pin()->GetService<FChangelistService>();
// Don't bother with the default changelist
if(ChangelistService->GetCLID() == TEXT("default"))
{
if(bRequeue)
{
QueueFetch(bRequeue, Definition.FetchPreflightEachSeconds);
}
return;
}
const FString& OIDCToken = CredentialsService->IsTokenReady() ? CredentialsService->GetToken() : InOAuthToken;
if(!OIDCToken.IsEmpty())
{
if(!LinkedPFRequest.IsValid())
{
LinkedPFRequest = FHttpModule::Get().CreateRequest();
FString FetchPreflightUrl = FString::Format(*Definition.FindPreflightURLFormat, GetFormatParameters());
LinkedPFRequest->SetURL(FetchPreflightUrl);
LinkedPFRequest->SetVerb(TEXT("GET"));
}
else if(LinkedPFRequest->GetStatus() == EHttpRequestStatus::Processing)
{
// if it's still Processing, do not try to request again.
return;
}
// ensure the token is the most up to date
LinkedPFRequest->SetHeader(TEXT("Authorization"), FString::Printf(TEXT("bearer %s"), *OIDCToken));
if(!LinkedPFRequest->OnProcessRequestComplete().IsBound())
{
LinkedPFRequest->OnProcessRequestComplete().Unbind();
}
LinkedPFRequest->OnProcessRequestComplete().BindLambda([this, bRequeue, &OIDCToken](FHttpRequestPtr Request, FHttpResponsePtr HttpResponse, bool bConnectedSuccessfully)
{
if(!bConnectedSuccessfully)
{
if(HttpResponse.IsValid())
{
UE_LOG(LogSubmitTool, Warning, TEXT("Unable to connect to horde. Connection error %d"), HttpResponse->GetResponseCode());
UE_LOG(LogSubmitToolDebug, Warning, TEXT("Unable to connect to horde. Connection error\nResponse: %s"), *HttpResponse->GetContentAsString());
}
else
{
UE_LOG(LogSubmitTool, Warning, TEXT("Unable to connect to horde. Connection error, no response."));
}
OnHordeConnectionFailed.Broadcast();
return;
}
if(HttpResponse.IsValid())
{
//UE_LOG(LogSubmitToolDebug, Verbose, TEXT("Fetch Preflight Response: %s"), *HttpResponse->GetContentAsString());
if(EHttpResponseCodes::IsOk(HttpResponse->GetResponseCode()))
{
TUniquePtr<FPreflightList> NewHordePreflights = MakeUnique<FPreflightList>();
FJsonObjectConverter::JsonObjectStringToUStruct<FPreflightList>(FString::Printf(TEXT("{\"PreflightList\" : %s}"), *HttpResponse->GetContentAsString()), NewHordePreflights.Get());
NewHordePreflights->Initialize();
if(PreflightTag != nullptr && !Definition.FindSinglePreflightURLFormat.IsEmpty())
{
for(FString PreflightId : PreflightTag->GetValues())
{
if(PreflightId.Equals(TEXT("skip")) || PreflightId.Equals(TEXT("none")))
{
continue;
}
if(PreflightId.Contains(TEXT("/")))
{
int32 SlashIdx;
PreflightId.FindLastChar(TCHAR('/'), SlashIdx);
PreflightId = PreflightId.RightChop(SlashIdx + 1);
}
PreflightId.TrimStartAndEndInline();
FRegexPattern Pattern = FRegexPattern(TEXT("(?:[0-9]|[a-f]){24}"), ERegexPatternFlags::CaseInsensitive);
FRegexMatcher regex = FRegexMatcher(Pattern, PreflightId);
bool match = regex.FindNext();
if(match)
{
const FPreflightData* FoundData = NewHordePreflights->PreflightList.FindByPredicate([&PreflightId](const FPreflightData& InData) { return InData.ID == PreflightId; });
if(FoundData == nullptr)
{
FetchUnlinkedPreflight(PreflightId, bRequeue, OIDCToken);
}
}
}
}
if(!HordePreflights.IsValid() || *NewHordePreflights != *HordePreflights)
{
UE_LOG(LogSubmitToolDebug, Verbose, TEXT("Newer Preflight information received"));
// Only log when there's a different number of preflights
if(!HordePreflights.IsValid() || HordePreflights->PreflightList.Num() != NewHordePreflights->PreflightList.Num())
{
UE_LOG(LogSubmitTool, Log, TEXT("Retrieved %d preflights for CL %s"), NewHordePreflights->PreflightList.Num(), *ServiceProvider.Pin()->GetService<FChangelistService>()->GetCLID());
}
HordePreflights = MoveTemp(NewHordePreflights);
if(PreflightTag != nullptr)
{
bool bCLDescriptionModified = false;
if(HordePreflights->PreflightList.Num() != 0)
{
FString CurrentTagValue = PreflightTag->GetValuesText();
if(!bStopAskingTagUpdate && !CurrentTagValue.Contains(HordePreflights->PreflightList[0].ID))
{
if (FSubmitToolUserPrefs::Get()->bAutoUpdatePreflight)
{
ModelInterface->SetTagValues(*PreflightTag, HordePreflights->PreflightList[0].ID);
UE_LOG(LogSubmitTool, Log, TEXT("Tag %s has been updated with the latest associated preflight %sjob/%s"), *PreflightTag->Definition.GetTagId(), *Definition.HordeServerAddress, *HordePreflights->PreflightList[0].ID)
bCLDescriptionModified = true;
}
else
{
EDialogFactoryResult Result = ShowUpdatePreflightTagDialog();
if(Result == EDialogFactoryResult::FirstButton)
{
// Set the latest one as the tag value
ModelInterface->SetTagValues(*PreflightTag, HordePreflights->PreflightList[0].ID);
UE_LOG(LogSubmitTool, Log, TEXT("Tag %s has been updated with the latest associated preflight %sjob/%s"), *PreflightTag->Definition.GetTagId(), *Definition.HordeServerAddress, *HordePreflights->PreflightList[0].ID)
bCLDescriptionModified = true;
}
else
{
bStopAskingTagUpdate = true;
}
}
}
}
if(bCLDescriptionModified)
{
ModelInterface->ValidateCLDescription();
}
}
}
}
else
{
UE_LOG(LogSubmitTool, Warning, TEXT("Could not retrieve preflights, Http code %d."), HttpResponse->GetResponseCode());
UE_LOG(LogSubmitToolDebug, Error, TEXT("Fetch preflight failed. Response %s"), *HttpResponse->GetContentAsString());
}
}
else
{
UE_LOG(LogSubmitTool, Warning, TEXT("Unable to fetch preflights. Failed with code %d"), HttpResponse->GetResponseCode());
UE_LOG(LogSubmitToolDebug, Warning, TEXT("Unable to fetch preflights. Failed with code %d\nResponse: %s"), HttpResponse->GetResponseCode(), *HttpResponse->GetContentAsString());
}
if(ActiveUnlinkedRequests == 0)
{
if(OnPreflightDataUpdated.IsBound() && HordePreflights.IsValid())
{
OnPreflightDataUpdated.Broadcast(HordePreflights, UnlinkedHordePreflights);
}
if(bRequeue)
{
Requeue();
}
}
});
FTimespan TimeSinceLast = FDateTime::UtcNow() - LastRequest;
if(bRequeue || TimeSinceLast.GetTotalSeconds() > 3)
{
LastRequest = FDateTime::UtcNow();
UE_LOG(LogSubmitToolDebug, Log, TEXT("Fetching preflights for CL %s. URL: %s"), *ChangelistService->GetCLID(), *LinkedPFRequest->GetURL())
LinkedPFRequest->ProcessRequest();
}
}
else
{
CredentialsService->QueueWorkForToken([this, bRequeue](const FString& InToken)
{
if(!InToken.IsEmpty())
{
FetchPreflightInfo(bRequeue, InToken);
}
else
{
UE_LOG(LogSubmitTool, Warning, TEXT("Couldn't obtain OAuth token login, communication with Horde is not possible."));
}
});
}
}
void FPreflightService::FetchUnlinkedPreflight(const FString& InPreflightId, bool bRequeue, const FString& InOAuthToken)
{
if(InOAuthToken.IsEmpty())
{
return;
}
FHttpRequestPtr& UnlinkedPFRequest = UnlinkedPFRequests.FindOrAdd(InPreflightId);
if(!UnlinkedPFRequest.IsValid())
{
UnlinkedPFRequest = FHttpModule::Get().CreateRequest();
FStringFormatNamedArguments ReplaceStringArgs = GetFormatParameters();
ReplaceStringArgs.Add(TEXT("PreflightId"), InPreflightId);
FString FetchPreflightUrl = FString::Format(*Definition.FindSinglePreflightURLFormat, ReplaceStringArgs);
UnlinkedPFRequest->SetURL(FetchPreflightUrl);
UnlinkedPFRequest->SetVerb(TEXT("GET"));
}
else if(UnlinkedPFRequest->GetStatus() == EHttpRequestStatus::Processing)
{
// if it's still Processing, do not try to request again.
return;
}
UnlinkedPFRequest->SetHeader(TEXT("Authorization"), FString::Printf(TEXT("bearer %s"), *InOAuthToken));
if(!UnlinkedPFRequest->OnProcessRequestComplete().IsBound())
{
UnlinkedPFRequest->OnProcessRequestComplete().Unbind();
}
ActiveUnlinkedRequests++;
UnlinkedPFRequest->OnProcessRequestComplete().BindLambda([this, bRequeue, &InOAuthToken, InPreflightId](FHttpRequestPtr Request, FHttpResponsePtr HttpResponse, bool bConnectedSuccessfully)
{
ActiveUnlinkedRequests--;
if(!bConnectedSuccessfully)
{
if(HttpResponse.IsValid())
{
UE_LOG(LogSubmitTool, Warning, TEXT("Unable to connect to horde. Connection error %d"), HttpResponse->GetResponseCode());
UE_LOG(LogSubmitToolDebug, Warning, TEXT("Unable to connect to horde. Connection error\nResponse: %s"), *HttpResponse->GetContentAsString());
}
else
{
UE_LOG(LogSubmitTool, Warning, TEXT("Unable to connect to horde. Connection error, no response."));
}
return;
}
if(HttpResponse.IsValid())
{
UE_LOG(LogSubmitToolDebug, Verbose, TEXT("Fetch Single Preflight Response: %s"), *HttpResponse->GetContentAsString());
if(EHttpResponseCodes::IsOk(HttpResponse->GetResponseCode()))
{
FPreflightData ReceivedPreflightInfo;
FJsonObjectConverter::JsonObjectStringToUStruct<FPreflightData>(*HttpResponse->GetContentAsString(), &ReceivedPreflightInfo);
ReceivedPreflightInfo.RecalculateCachedResults();
if(!UnlinkedHordePreflights.Contains(InPreflightId) || UnlinkedHordePreflights[InPreflightId] != ReceivedPreflightInfo)
{
UE_LOG(LogSubmitToolDebug, Verbose, TEXT("Newer %s Preflight information received"), *InPreflightId);
// Only log when the preflight is new
if(!UnlinkedHordePreflights.Contains(InPreflightId))
{
UE_LOG(LogSubmitTool, Log, TEXT("Retrieved information from preflight %s"), *InPreflightId);
UnlinkedHordePreflights.Add(InPreflightId, ReceivedPreflightInfo);
}
else
{
UnlinkedHordePreflights[InPreflightId] = MoveTemp(ReceivedPreflightInfo);
}
}
}
else
{
UE_LOG(LogSubmitTool, Warning, TEXT("Could not retrieve preflights, Http code %d."), HttpResponse->GetResponseCode());
UE_LOG(LogSubmitToolDebug, Error, TEXT("Fetch preflight failed. Response %s"), *HttpResponse->GetContentAsString());
}
}
else
{
UE_LOG(LogSubmitTool, Warning, TEXT("Unable to fetch preflights. Failed with code %d"), HttpResponse->GetResponseCode());
UE_LOG(LogSubmitToolDebug, Warning, TEXT("Unable to fetch preflights. Failed with code %d\nResponse: %s"), HttpResponse->GetResponseCode(), *HttpResponse->GetContentAsString());
}
if(ActiveUnlinkedRequests == 0)
{
if(OnPreflightDataUpdated.IsBound() && HordePreflights.IsValid())
{
OnPreflightDataUpdated.Broadcast(HordePreflights, UnlinkedHordePreflights);
}
if(bRequeue)
{
Requeue();
}
}
});
UnlinkedPFRequest->ProcessRequest();
}
void FPreflightService::StartPreflight()
{
TSharedPtr<ISTSourceControlService> SCCService = ServiceProvider.Pin()->GetService<ISTSourceControlService>();
StreamName = SCCService->GetRootStreamName();
const TArray<FString>& ShelvedPaths = ServiceProvider.Pin()->GetService<FChangelistService>()->GetShelvedFilesDepotPaths(true);
if (ShelvedPaths.IsEmpty())
{
LastErrorMessage = TEXT("Shelve is empty or it couldn't be retrieved from p4, can't request preflight");
State = EPreflightServiceState::Error;
return;
}
FString CommonPath = ShelvedPaths[0];
const FString& LastPath = ShelvedPaths.Last();
for(size_t i = 0; i < FMath::Min(CommonPath.Len(), LastPath.Len()); ++i)
{
if(CommonPath[i] != LastPath[i])
{
CommonPath = CommonPath.Left(i);
break;
}
}
if(!CommonPath.Equals(TEXT("//")))
{
int32 NextSlash = CommonPath.Find(TEXT("/"), ESearchCase::IgnoreCase, ESearchDir::FromStart, 2);
const FString Depot = CommonPath.Mid(2, NextSlash - 2);
size_t StreamDepth = SCCService->GetDepotStreamLength(Depot);
for(size_t i = 0; i < StreamDepth; ++i)
{
NextSlash = CommonPath.Find(TEXT("/"), ESearchCase::IgnoreCase, ESearchDir::FromStart, NextSlash+1);
}
if(NextSlash != -1)
{
const FSCCStream* FoundStream = SCCService->GetSCCStream(CommonPath.Left(NextSlash));
if(FoundStream != nullptr)
{
StreamName = FoundStream->Name;
}
}
}
FString StartPreflightUrl = FString::Format(*Definition.StartPreflightURLFormat, GetFormatParameters());
// If for some reason, our preflight settings are missing, this will be empty, let's not popup a browser with nothing in it
if(!StartPreflightUrl.IsEmpty())
{
UE_LOG(LogSubmitTool, Log, TEXT("Preflight: Starting preflight with URL: \"%s\""), *StartPreflightUrl);
FTelemetryService::Get()->CustomEvent(TEXT("SubmitTool.PreflightLaunched"), MakeAnalyticsEventAttributeArray(
TEXT("PreflightURL"), StartPreflightUrl,
TEXT("Stream"), StreamName
));
FPlatformProcess::LaunchURL(*StartPreflightUrl, nullptr, nullptr);
State = EPreflightServiceState::Idle;
// Do a Fetch in 10 and 30 s to try and capture the triggered preflight
QueueFetch(false, 10.f);
QueueFetch(false, 30.f);
}
else
{
LastErrorMessage = TEXT("Missing INI preflight settings");
State = EPreflightServiceState::Error;
}
}
void FPreflightService::OnDeleteShelveOperationComplete(const FSourceControlOperationRef& Operation, ECommandResult::Type Result)
{
if(State == EPreflightServiceState::WaitingForDeleteShelve)
{
if(Result == ECommandResult::Type::Succeeded)
{
State = EPreflightServiceState::RequestCreateShelve;
}
else
{
LastErrorMessage = TEXT("Unable to delete shelve for preflight");
State = EPreflightServiceState::Error;
}
}
else
{
LastErrorMessage = TEXT("Received delete shelve callback when not waiting for it");
State = EPreflightServiceState::Error;
}
}
void FPreflightService::OnCreateShelveOperationComplete(const FSourceControlOperationRef& Operation, ECommandResult::Type Result)
{
if(State == EPreflightServiceState::WaitingForCreateShelve)
{
if(Result == ECommandResult::Type::Succeeded)
{
State = EPreflightServiceState::StartPreflight;
}
else
{
LastErrorMessage = TEXT("Unable to create shelve for preflight");
State = EPreflightServiceState::Error;
}
}
else
{
LastErrorMessage = TEXT("Received create shelve callback when not waiting for it");
State = EPreflightServiceState::Error;
}
}
bool FPreflightService::SelectPreflightTemplate(FPreflightTemplateDefinition& OutTemplate) const
{
TSharedPtr<FChangelistService> ChangelistService = ServiceProvider.Pin()->GetService<FChangelistService>();
// Get the list of files in the changelist
const TArray<FString>& FilesInCl = bCheckShelveInstead ? ChangelistService->GetShelvedFilesDepotPaths() : ChangelistService->GetFilesDepotPaths();
// Loop through each definition to see if the files are in the path then check extension
for(const FPreflightTemplateDefinition& Def : Definition.Definitions)
{
FString RegexPat = Def.RegexPath.Replace(TEXT("$(StreamRoot)"), *StreamName, ESearchCase::IgnoreCase);
FRegexPattern Pattern = FRegexPattern(RegexPat, ERegexPatternFlags::CaseInsensitive);
for(const FString& File : FilesInCl)
{
FRegexMatcher regex = FRegexMatcher(Pattern, File);
if(regex.FindNext())
{
OutTemplate = Def;
return true;
}
}
}
return false;
}
FString FPreflightService::GetAdditionalTasksString(const FPreflightTemplateDefinition& InTemplate) const
{
TStringBuilder<256> AdditionalTaskStrBuilder;
const FString BaseString = TEXT("&id-additional-tasks.");
const FString EndString = TEXT("=true");
TSharedPtr<FChangelistService> ChangelistService = ServiceProvider.Pin()->GetService<FChangelistService>();
const TArray<FString>& FilesInCl = bCheckShelveInstead ? ChangelistService->GetShelvedFilesDepotPaths() : ChangelistService->GetFilesDepotPaths();
for(const FPreflightAdditionalTask& AdditionalTask : InTemplate.AdditionalTasks)
{
FString RegexPat = AdditionalTask.RegexPath.Replace(TEXT("$(StreamRoot)"), *StreamName, ESearchCase::IgnoreCase);
FRegexPattern Pattern = FRegexPattern(RegexPat, ERegexPatternFlags::CaseInsensitive);
for(const FString& File : FilesInCl)
{
FRegexMatcher regex = FRegexMatcher(Pattern, File);
if(regex.FindNext())
{
AdditionalTaskStrBuilder.Append(BaseString);
AdditionalTaskStrBuilder.Append(AdditionalTask.TaskId);
AdditionalTaskStrBuilder.Append(EndString);
}
}
}
return AdditionalTaskStrBuilder.ToString();
}
EDialogFactoryResult FPreflightService::ShowRecreateShelveDialog() const
{
if(ModelInterface->GetMainTab().IsValid() && ModelInterface->GetMainTab().Pin()->GetParentWindow().IsValid())
{
ModelInterface->GetMainTab().Pin()->GetParentWindow()->DrawAttention(FWindowDrawAttentionParameters());
}
const FText TextTitle = FText::FromString(FString::Printf(TEXT("Preflight CL %s: Recreate shelved files?"), *ServiceProvider.Pin()->GetService<FChangelistService>()->GetCLID()));
const FText TextDescription = FText::FromString(TEXT("There are already shelved files in this changelist.\n\nDo you want to delete and recreate your shelf from the latest changes in your local files for use in the preflight?"));
return FDialogFactory::ShowDialog(TextTitle, TextDescription, TArray<FString>{ TEXT("Re-shelve files"), TEXT("Use existing Shelve"), TEXT("Cancel") });
}
EDialogFactoryResult FPreflightService::ShowUpdatePreflightTagDialog() const
{
if(ModelInterface->GetMainTab().IsValid() && ModelInterface->GetMainTab().Pin()->GetParentWindow().IsValid())
{
ModelInterface->GetMainTab().Pin()->GetParentWindow()->DrawAttention(FWindowDrawAttentionParameters());
}
const FText TextTitle = FText::FromString(FString::Printf(TEXT("Preflight CL %s: Newer preflight available"), *ServiceProvider.Pin()->GetService<FChangelistService>()->GetCLID()));
const FText TextDescription = FText::FromString(FString::Printf(TEXT("There is a newer preflight for this changelist:\n<a id=\"browser\" style=\"Hyperlink\" href=\"%sjob/%s\">%s - %s</>\n\nDo you want to update the #preflight tag?"), *Definition.HordeServerAddress, *HordePreflights->PreflightList[0].ID, *HordePreflights->PreflightList[0].Name, *HordePreflights->PreflightList[0].ID));
TSharedPtr<SHorizontalBox> AutoUpdate = SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SCheckBox)
.IsChecked_Lambda([]() { return FSubmitToolUserPrefs::Get()->bAutoUpdatePreflight ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })
.OnCheckStateChanged_Lambda([](ECheckBoxState InNewState)
{
FSubmitToolUserPrefs::Get()->bAutoUpdatePreflight = InNewState == ECheckBoxState::Checked;
})
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "InvisibleButton")
.IsFocusable(false)
.OnClicked_Lambda([this]() { FSubmitToolUserPrefs::Get()->bAutoUpdatePreflight = !FSubmitToolUserPrefs::Get()->bAutoUpdatePreflight; return FReply::Handled(); })
[
SNew(STextBlock)
.Justification(ETextJustify::Left)
.MinDesiredWidth(60)
.Text(FText::FromString(TEXT("Always update, Don't ask again")))
]
];
return FDialogFactory::ShowDialog(TextTitle, TextDescription, TArray<FString>{ TEXT("Update Tag Value"), TEXT("Cancel") }, AutoUpdate);
}