bool FPerforceSyncWorker::Execute(FPerforceSourceControlCommand& InCommand)
{
	FScopedPerforceConnection ScopedConnection(InCommand);
	if (!InCommand.IsCanceled() && ScopedConnection.IsValid())
	{
		FPerforceConnection& Connection = ScopedConnection.GetConnection();
		TArray<FString> Parameters;
		Parameters.Append(InCommand.Files);

		// check for directories and add '...'
		for(auto& FileName : Parameters)
		{
			if(FileName.EndsWith(TEXT("/")))
			{
				FileName += TEXT("...");
			}
		}

		FP4RecordSet Records;
		InCommand.bCommandSuccessful = Connection.RunCommand(TEXT("sync"), Parameters, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped);
		ParseSyncResults(Records, OutResults);

		RemoveRedundantErrors(InCommand, TEXT("file(s) up-to-date"));
	}
	return InCommand.bCommandSuccessful;
}
bool FPerforceSourceControlLabel::Sync( const TArray<FString>& InFilenames ) const
{
	bool bCommandOK = false;

	FPerforceSourceControlModule& PerforceSourceControl = FModuleManager::LoadModuleChecked<FPerforceSourceControlModule>("PerforceSourceControl");
	FScopedPerforceConnection ScopedConnection(EConcurrency::Synchronous, PerforceSourceControl.AccessSettings().GetConnectionInfo());
	if(ScopedConnection.IsValid())
	{
		FPerforceConnection& Connection = ScopedConnection.GetConnection();
		FP4RecordSet Records;
		TArray<FText> ErrorMessages;
		TArray<FString> Parameters;
		for(const auto& Filename : InFilenames)
		{
			Parameters.Add(Filename + TEXT("@") + Name);
		}
		bool bConnectionDropped = false;
		bCommandOK = Connection.RunCommand(TEXT("sync"), Parameters, Records, ErrorMessages, FOnIsCancelled(), bConnectionDropped);
		if(!bCommandOK)
		{
			// output errors if any
			for (int32 ErrorIndex = 0; ErrorIndex < ErrorMessages.Num(); ++ErrorIndex)
			{
				FMessageLog("SourceControl").Error(ErrorMessages[ErrorIndex]);
			}
		}
	}

	return bCommandOK;
}
bool FPerforceMarkForAddWorker::Execute(FPerforceSourceControlCommand& InCommand)
{
	// Perforce will allow you to mark files for add that don't currently exist on disk
	// This goes against the workflow of our other SCC providers (such as SVN and Git), 
	// so we manually check that the files exist before allowing this command to continue
	// This keeps the behavior consistent between SCC providers
	bool bHasMissingFiles = false;
	for(const FString& FileToAdd : InCommand.Files)
	{
		if(!IFileManager::Get().FileExists(*FileToAdd))
		{
			bHasMissingFiles = true;
			InCommand.ErrorMessages.Add(FText::Format(LOCTEXT("Error_FailedToMarkFileForAdd_FileMissing", "Failed mark the file '{0}' for add. The file doesn't exist on disk."), FText::FromString(FileToAdd)));
		}
	}
	if(bHasMissingFiles)
	{
		InCommand.bCommandSuccessful = false;
		return false;
	}

	FScopedPerforceConnection ScopedConnection(InCommand);
	if (!InCommand.IsCanceled() && ScopedConnection.IsValid())
	{
		FPerforceConnection& Connection = ScopedConnection.GetConnection();
		TArray<FString> Parameters;
		FP4RecordSet Records;
		Parameters.Append(InCommand.Files);
		InCommand.bCommandSuccessful = Connection.RunCommand(TEXT("add"), Parameters, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped);
		ParseRecordSetForState(Records, OutResults);
	}
	return InCommand.bCommandSuccessful;
}
bool FPerforceSourceControlLabel::GetFileRevisions( const TArray<FString>& InFiles, TArray< TSharedRef<ISourceControlRevision, ESPMode::ThreadSafe> >& OutRevisions ) const
{
	bool bCommandOK = false;

	FPerforceSourceControlModule& PerforceSourceControl = FModuleManager::LoadModuleChecked<FPerforceSourceControlModule>("PerforceSourceControl");
	FScopedPerforceConnection ScopedConnection(EConcurrency::Synchronous, PerforceSourceControl.AccessSettings().GetConnectionInfo());
	if(ScopedConnection.IsValid())
	{
		FPerforceConnection& Connection = ScopedConnection.GetConnection();
		FP4RecordSet Records;
		TArray<FString> Parameters;
		TArray<FText> ErrorMessages;
		for(auto Iter(InFiles.CreateConstIterator()); Iter; Iter++)
		{
			Parameters.Add(*Iter + TEXT("@") + Name);
		}
		bool bConnectionDropped = false;
		bCommandOK = Connection.RunCommand(TEXT("files"), Parameters, Records, ErrorMessages, FOnIsCancelled(), bConnectionDropped);
		if(bCommandOK)
		{
			ParseFilesResults(Records, OutRevisions, Connection.ClientRoot);
		}
		else
		{
			// output errors if any
			for (int32 ErrorIndex = 0; ErrorIndex < ErrorMessages.Num(); ++ErrorIndex)
			{
				FMessageLog("SourceControl").Error(ErrorMessages[ErrorIndex]);
			}
		}
	}

	return bCommandOK;
}
bool FPerforceCheckOutWorker::Execute(FPerforceSourceControlCommand& InCommand)
{	
	FScopedPerforceConnection ScopedConnection(InCommand);
	if (!InCommand.IsCanceled() && ScopedConnection.IsValid())
	{
		FPerforceConnection& Connection = ScopedConnection.GetConnection();
		TArray<FString> Parameters;

		FPerforceSourceControlModule& PerforceSourceControl = FModuleManager::LoadModuleChecked<FPerforceSourceControlModule>("PerforceSourceControl");
		FPerforceSourceControlSettings& Settings = PerforceSourceControl.AccessSettings();
		if (Settings.GetChangelistNumber().IsEmpty() == false)
		{
			Parameters.Add(TEXT("-c"));
			Parameters.Add(Settings.GetChangelistNumber());
			// Parameters.Add(FString::Printf(TEXT("-c %s"), *Settings.GetChangelistNumber()));
		}

		Parameters.Append(InCommand.Files);
		FP4RecordSet Records;
		InCommand.bCommandSuccessful = Connection.RunCommand(TEXT("edit"), Parameters, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped);
		ParseRecordSetForState(Records, OutResults);
	}

	return InCommand.bCommandSuccessful;
}
bool FPerforceCopyWorker::Execute(class FPerforceSourceControlCommand& InCommand)
{
	FScopedPerforceConnection ScopedConnection(InCommand);
	if (!InCommand.IsCanceled() && ScopedConnection.IsValid())
	{
		FPerforceConnection& Connection = ScopedConnection.GetConnection();

		check(InCommand.Operation->GetName() == GetName());
		TSharedRef<FCopy, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FCopy>(InCommand.Operation);

		FString DestinationPath = FPaths::ConvertRelativePathToFull(Operation->GetDestination());

		TArray<FString> Parameters;
		Parameters.Append(InCommand.Files);
		Parameters.Add(DestinationPath);
		
		FP4RecordSet Records;
		InCommand.bCommandSuccessful = Connection.RunCommand(TEXT("integrate"), Parameters, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped);

		// We now need to do a p4 resolve.
		// This is because when we copy a file in the Editor, we first make the copy on disk before attempting to branch. This causes a conflict in P4's eyes.
		// We must do this to prevent the asset registry from picking up what it thinks is a newly-added file (which would be created by the p4 integrate command)
		// and then the package system getting very confused about where to save the now-duplicated assets.
		if(InCommand.bCommandSuccessful)
		{
			TArray<FString> ResolveParameters;
			ResolveParameters.Add(TEXT("-ay"));	// 'accept yours'
			ResolveParameters.Add(DestinationPath);
			InCommand.bCommandSuccessful = Connection.RunCommand(TEXT("resolve"), ResolveParameters, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped);
		}
	}
	return InCommand.bCommandSuccessful;
}
TArray< TSharedRef<ISourceControlLabel> > FPerforceSourceControlProvider::GetLabels( const FString& InMatchingSpec ) const
{
	TArray< TSharedRef<ISourceControlLabel> > Labels;

	FPerforceSourceControlModule& PerforceSourceControl = FModuleManager::LoadModuleChecked<FPerforceSourceControlModule>("PerforceSourceControl");
	FScopedPerforceConnection ScopedConnection(EConcurrency::Synchronous, PerforceSourceControl.AccessSettings().GetConnectionInfo());
	if(ScopedConnection.IsValid())
	{
		FPerforceConnection& Connection = ScopedConnection.GetConnection();
		FP4RecordSet Records;
		TArray<FString> Parameters;
		TArray<FText> ErrorMessages;
		Parameters.Add(TEXT("-E"));
		Parameters.Add(InMatchingSpec);
		bool bConnectionDropped = false;
		if(Connection.RunCommand(TEXT("labels"), Parameters, Records, ErrorMessages, FOnIsCancelled(), bConnectionDropped))
		{
			ParseGetLabelsResults(Records, Labels);
		}
		else
		{
			// output errors if any
			for (int32 ErrorIndex = 0; ErrorIndex < ErrorMessages.Num(); ++ErrorIndex)
			{
				FMessageLog("SourceControl").Warning(ErrorMessages[ErrorIndex]);
			}
		}
	}

	return Labels;
}
bool FPerforceRevertWorker::Execute(FPerforceSourceControlCommand& InCommand)
{
	FScopedPerforceConnection ScopedConnection(InCommand);
	if (!InCommand.IsCanceled() && ScopedConnection.IsValid())
	{
		FPerforceConnection& Connection = ScopedConnection.GetConnection();
		TArray<FString> Parameters;
		Parameters.Append(InCommand.Files);
		FP4RecordSet Records;
		InCommand.bCommandSuccessful = Connection.RunCommand(TEXT("revert"), Parameters, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped);
		ParseRecordSetForState(Records, OutResults);
	}
	return InCommand.bCommandSuccessful;
}
bool FPerforceGetWorkspacesWorker::Execute(FPerforceSourceControlCommand& InCommand)
{
	FScopedPerforceConnection ScopedConnection(InCommand);
	if (!InCommand.IsCanceled() && ScopedConnection.IsValid())
	{
		FPerforceConnection& Connection = ScopedConnection.GetConnection();
		TArray<FString> ClientSpecList;
		InCommand.bCommandSuccessful = Connection.GetWorkspaceList(InCommand.ConnectionInfo, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), ClientSpecList, InCommand.ErrorMessages);

		check(InCommand.Operation->GetName() == GetName());
		TSharedRef<FGetWorkspaces, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FGetWorkspaces>(InCommand.Operation);
		Operation->Results = ClientSpecList;
	}
	return InCommand.bCommandSuccessful;
}
bool FPerforceResolveWorker::Execute(class FPerforceSourceControlCommand& InCommand) 
{
	FScopedPerforceConnection ScopedConnection(InCommand);
	if (!InCommand.IsCanceled() && ScopedConnection.IsValid())
	{
		FPerforceConnection& Connection = ScopedConnection.GetConnection();

		TArray<FString> Parameters;
		Parameters.Add("-ay");
		Parameters.Append(InCommand.Files);

		FP4RecordSet Records;
		InCommand.bCommandSuccessful = Connection.RunCommand(TEXT("resolve"), Parameters, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped);
		if( InCommand.bCommandSuccessful )
		{
			UpdatedFiles = InCommand.Files;
		}
	}

	return InCommand.bCommandSuccessful;
}
bool FPerforceConnectWorker::Execute(FPerforceSourceControlCommand& InCommand)
{
	FScopedPerforceConnection ScopedConnection(InCommand);
	if (!InCommand.IsCanceled() && ScopedConnection.IsValid())
	{
		FPerforceConnection& Connection = ScopedConnection.GetConnection();
		TArray<FString> Parameters;
		FP4RecordSet Records;
		Parameters.Add(TEXT("-o"));
		Parameters.Add(InCommand.ConnectionInfo.Workspace);
		InCommand.bCommandSuccessful = Connection.RunCommand(TEXT("client"), Parameters, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped);
		
		// If there are error messages, user name is most likely invalid. Otherwise, make sure workspace actually
		// exists on server by checking if we have it's update date.
		InCommand.bCommandSuccessful &= InCommand.ErrorMessages.Num() == 0 && Records.Num() > 0 && Records[0].Contains(TEXT("Update"));
		if (!InCommand.bCommandSuccessful && InCommand.ErrorMessages.Num() == 0)
		{
			InCommand.ErrorMessages.Add(LOCTEXT("InvalidWorkspace", "Invalid workspace."));
		}

		// check if we can actually work with this workspace
		if(InCommand.bCommandSuccessful)
		{
			FText Notification;
			InCommand.bCommandSuccessful = CheckWorkspaceRecordSet(Records, InCommand.ErrorMessages, Notification);
			if(!InCommand.bCommandSuccessful)
			{
				check(InCommand.Operation->GetName() == GetName());
				TSharedRef<FConnect, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FConnect>(InCommand.Operation);
				Operation->SetErrorText(Notification);
			}
		}

		if(InCommand.bCommandSuccessful)
		{
			ParseRecordSet(Records, InCommand.InfoMessages);
		}
	}
	return InCommand.bCommandSuccessful;
}
bool FPerforceUpdateStatusWorker::Execute(FPerforceSourceControlCommand& InCommand)
{
#if USE_P4_API
	FScopedPerforceConnection ScopedConnection(InCommand);
	if (!InCommand.IsCanceled() && ScopedConnection.IsValid())
	{
		FPerforceConnection& Connection = ScopedConnection.GetConnection();
		if(InCommand.Files.Num())
		{
			// See http://www.perforce.com/perforce/doc.current/manuals/cmdref/p4_fstat.html
			// for full reference info on fstat command parameters...

			TArray<FString> Parameters;

			// We want to include integration record information:
			Parameters.Add(TEXT("-Or"));

			// Mandatory parameters (the list of files to stat):
			for (FString& File : InCommand.Files)
			{
				if (IFileManager::Get().DirectoryExists(*File))
				{
					// If the file is a directory, do a recursive fstat on the contents
					File /= TEXT("...");
				}

				Parameters.Add(File);
			}

			FP4RecordSet Records;
			InCommand.bCommandSuccessful = Connection.RunCommand(TEXT("fstat"), Parameters, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped);
			ParseUpdateStatusResults(Records, InCommand.ErrorMessages, OutStates);
			RemoveRedundantErrors(InCommand, TEXT(" - no such file(s)."));
			RemoveRedundantErrors(InCommand, TEXT("' is not under client's root '"));
		}
		else
		{
			InCommand.bCommandSuccessful = true;
		}

		// update using any special hints passed in via the operation
		check(InCommand.Operation->GetName() == GetName());
		TSharedRef<FUpdateStatus, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FUpdateStatus>(InCommand.Operation);

		if(Operation->ShouldUpdateHistory())
		{
			TArray<FString> Parameters;
			FP4RecordSet Records;
			// disregard non-contributory integrations
			Parameters.Add(TEXT("-s"));
			//include branching history
			Parameters.Add(TEXT("-i"));
			//include truncated change list descriptions
			Parameters.Add(TEXT("-L"));
			//include time stamps
			Parameters.Add(TEXT("-t"));
			//limit to last 100 changes
			Parameters.Add(TEXT("-m 100"));
			Parameters.Append(InCommand.Files);
			InCommand.bCommandSuccessful &= Connection.RunCommand(TEXT("filelog"), Parameters, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped);
			ParseHistoryResults(Records, OutStates, OutHistory);
			RemoveRedundantErrors(InCommand, TEXT(" - no such file(s)."));
			RemoveRedundantErrors(InCommand, TEXT(" - file(s) not on client"));
			RemoveRedundantErrors(InCommand, TEXT("' is not under client's root '"));
		}

		if(Operation->ShouldGetOpenedOnly())
		{
			const FString ContentFolder = FPaths::ConvertRelativePathToFull(FPaths::RootDir());
			const FString FileQuery = FString::Printf(TEXT("%s..."), *ContentFolder);
			TArray<FString> Parameters = InCommand.Files;
			Parameters.Add(FileQuery);
			FP4RecordSet Records;
			InCommand.bCommandSuccessful &= Connection.RunCommand(TEXT("opened"), Parameters, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped);
			ParseOpenedResults(Records, ANSI_TO_TCHAR(Connection.P4Client.GetClient().Text()), Connection.ClientRoot, OutStateMap);
			RemoveRedundantErrors(InCommand, TEXT(" - no such file(s)."));
			RemoveRedundantErrors(InCommand, TEXT("' is not under client's root '"));
		}

		if(Operation->ShouldUpdateModifiedState())
		{
			TArray<FString> Parameters;
			FP4RecordSet Records;
			// Query for open files different than the versions stored in Perforce
			Parameters.Add(TEXT("-sa"));
			Parameters.Append(InCommand.Files);
			InCommand.bCommandSuccessful &= Connection.RunCommand(TEXT("diff"), Parameters, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped);

			// Parse the results and store them in the command
			ParseDiffResults(Records, OutModifiedFiles);
			RemoveRedundantErrors(InCommand, TEXT(" - no such file(s)."));
			RemoveRedundantErrors(InCommand, TEXT(" - file(s) not opened for edit"));
			RemoveRedundantErrors(InCommand, TEXT("' is not under client's root '"));
		}
	}
#endif

	return InCommand.bCommandSuccessful;
}
bool FPerforceCheckInWorker::Execute(FPerforceSourceControlCommand& InCommand)
{
	FScopedPerforceConnection ScopedConnection(InCommand);
	if (!InCommand.IsCanceled() && ScopedConnection.IsValid())
	{	
		FPerforceConnection& Connection = ScopedConnection.GetConnection();

		check(InCommand.Operation->GetName() == GetName());
		TSharedRef<FCheckIn, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FCheckIn>(InCommand.Operation);

		int32 ChangeList = Connection.CreatePendingChangelist(Operation->GetDescription(), FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.ErrorMessages);
		if (ChangeList > 0)
		{
			// Batch reopen into multiple commands, to avoid command line limits
			const int32 BatchedCount = 100;
			InCommand.bCommandSuccessful = true;
			for (int32 StartingIndex = 0; StartingIndex < InCommand.Files.Num() && InCommand.bCommandSuccessful; StartingIndex += BatchedCount)
			{
				FP4RecordSet Records;
				TArray< FString > ReopenParams;
						
				//Add changelist information to params
				ReopenParams.Insert(TEXT("-c"), 0);
				ReopenParams.Insert(FString::Printf(TEXT("%d"), ChangeList), 1);
				int32 NextIndex = FMath::Min(StartingIndex + BatchedCount, InCommand.Files.Num());

				for (int32 FileIndex = StartingIndex; FileIndex < NextIndex; FileIndex++)
				{
					ReopenParams.Add(InCommand.Files[FileIndex]);
				}

				InCommand.bCommandSuccessful = Connection.RunCommand(TEXT("reopen"), ReopenParams, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped);
			}

			if (InCommand.bCommandSuccessful)
			{
				// Only submit if reopen was successful
				TArray<FString> SubmitParams;
				FP4RecordSet Records;

				SubmitParams.Insert(TEXT("-c"), 0);
				SubmitParams.Insert(FString::Printf(TEXT("%d"), ChangeList), 1);

				InCommand.bCommandSuccessful = Connection.RunCommand(TEXT("submit"), SubmitParams, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped);

				if (InCommand.ErrorMessages.Num() > 0)
				{
					InCommand.bCommandSuccessful = false;
				}

				if (InCommand.bCommandSuccessful)
				{
					// Remove any deleted files from status cache
					FPerforceSourceControlModule& PerforceSourceControl = FModuleManager::GetModuleChecked<FPerforceSourceControlModule>("PerforceSourceControl");
					FPerforceSourceControlProvider& Provider = PerforceSourceControl.GetProvider();

					TArray<TSharedRef<ISourceControlState, ESPMode::ThreadSafe>> States;
					Provider.GetState(InCommand.Files, States, EStateCacheUsage::Use);
					for (const auto& State : States)
					{
						if (State->IsDeleted())
						{
							Provider.RemoveFileFromCache(State->GetFilename());
						}
					}

					StaticCastSharedRef<FCheckIn>(InCommand.Operation)->SetSuccessMessage(ParseSubmitResults(Records));

					for(auto Iter(InCommand.Files.CreateIterator()); Iter; Iter++)
					{
						OutResults.Add(*Iter, EPerforceState::ReadOnly);
					}
				}
			}
		}
		else
		{
			// Failed to create the changelist
			InCommand.bCommandSuccessful = false;
		}
	}

	return InCommand.bCommandSuccessful;
}