uint32 FBuildPatchInstaller::Run()
{
    // Make sure this function can never be parallelized
    static FCriticalSection SingletonFunctionLockCS;
    FScopeLock SingletonFunctionLock(&SingletonFunctionLockCS);
    FBuildPatchInstallError::Reset();

    SetRunning(true);
    SetInited(true);
    SetDownloadSpeed(-1);
    UpdateDownloadProgressInfo(true);

    // Register the current manifest with the installation info, to make sure we pull from it
    if (CurrentBuildManifest.IsValid())
    {
        InstallationInfo.RegisterAppInstallation(CurrentBuildManifest.ToSharedRef(), InstallDirectory);
    }

    // Keep track of files that failed verify
    TArray<FString> CorruptFiles;

    // Init prereqs progress value
    const bool bInstallPrereqs = !CurrentBuildManifest.IsValid() && !NewBuildManifest->GetPrereqPath().IsEmpty();

    // Get the start time
    double StartTime = FPlatformTime::Seconds();
    double CleanUpTime = 0;

    // Keep retrying the install while it is not canceled, or caused by download error
    bool bProcessSuccess = false;
    bool bCanRetry = true;
    int32 InstallRetries = 5;
    while (!bProcessSuccess && bCanRetry)
    {
        // Run the install
        bool bInstallSuccess = RunInstallation(CorruptFiles);
        BuildProgress.SetStateProgress(EBuildPatchProgress::PrerequisitesInstall, bInstallPrereqs ? 0.0f : 1.0f);
        if (bInstallSuccess)
        {
            BuildProgress.SetStateProgress(EBuildPatchProgress::Downloading, 1.0f);
            BuildProgress.SetStateProgress(EBuildPatchProgress::Installing, 1.0f);
        }

        // Backup local changes then move generated files
        bInstallSuccess = bInstallSuccess && RunBackupAndMove();

        // Run Verification
        CorruptFiles.Empty();
        BuildProgress.SetStateProgress(EBuildPatchProgress::Initializing, 1.0f);
        bProcessSuccess = bInstallSuccess && RunVerification(CorruptFiles);

        // Clean staging if INSTALL success
        if (bInstallSuccess)
        {
            GLog->Logf(TEXT("BuildPatchServices: Deleting staging area"));
            CleanUpTime = FPlatformTime::Seconds();
            IFileManager::Get().DeleteDirectory(*StagingDirectory, false, true);
            CleanUpTime = FPlatformTime::Seconds() - CleanUpTime;
        }
        BuildProgress.SetStateProgress(EBuildPatchProgress::CleanUp, 1.0f);

        // Set if we can retry
        --InstallRetries;
        bCanRetry = InstallRetries > 0 && !FBuildPatchInstallError::IsInstallationCancelled() && !FBuildPatchInstallError::IsNoRetryError();

        // If successful or we will retry, remove the moved files marker
        if (bProcessSuccess || bCanRetry)
        {
            GLog->Logf(TEXT("BuildPatchServices: Reset MM"));
            IFileManager::Get().Delete(*PreviousMoveMarker, false, true);
        }
    }

    if (bProcessSuccess)
    {
        // Run the prerequisites installer if this is our first install and the manifest has prerequisites info
        if (bInstallPrereqs)
        {
            // @TODO: We also want to trigger prereq install if this is an update and the prereq installer differs in the update
            bProcessSuccess &= RunPrereqInstaller();
        }
    }

    // Set final stat values and log out results
    {
        FScopeLock Lock(&ThreadLock);
        bSuccess = bProcessSuccess;
        BuildStats.ProcessSuccess = bProcessSuccess;
        BuildStats.ProcessExecuteTime = (FPlatformTime::Seconds() - StartTime) - BuildStats.ProcessPausedTime;
        BuildStats.FailureReason = FBuildPatchInstallError::GetErrorString();
        BuildStats.FailureReasonText = FBuildPatchInstallError::GetErrorText();
        BuildStats.CleanUpTime = CleanUpTime;

        // Log stats
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: AppName: %s"), *BuildStats.AppName);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: AppInstalledVersion: %s"), *BuildStats.AppInstalledVersion);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: AppPatchVersion: %s"), *BuildStats.AppPatchVersion);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: CloudDirectory: %s"), *BuildStats.CloudDirectory);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: NumFilesInBuild: %u"), BuildStats.NumFilesInBuild);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: NumFilesOutdated: %u"), BuildStats.NumFilesOutdated);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: NumFilesToRemove: %u"), BuildStats.NumFilesToRemove);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: NumChunksRequired: %u"), BuildStats.NumChunksRequired);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: ChunksQueuedForDownload: %u"), BuildStats.ChunksQueuedForDownload);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: ChunksLocallyAvailable: %u"), BuildStats.ChunksLocallyAvailable);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: NumChunksDownloaded: %u"), BuildStats.NumChunksDownloaded);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: NumChunksRecycled: %u"), BuildStats.NumChunksRecycled);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: NumChunksCacheBooted: %u"), BuildStats.NumChunksCacheBooted);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: NumDriveCacheChunkLoads: %u"), BuildStats.NumDriveCacheChunkLoads);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: NumRecycleFailures: %u"), BuildStats.NumRecycleFailures);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: NumDriveCacheLoadFailures: %u"), BuildStats.NumDriveCacheLoadFailures);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: TotalDownloadedData: %lld"), BuildStats.TotalDownloadedData);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: AverageDownloadSpeed: %.3f MB/sec"), BuildStats.AverageDownloadSpeed / 1024.0 / 1024.0);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: TheoreticalDownloadTime: %s"), *FPlatformTime::PrettyTime(BuildStats.TheoreticalDownloadTime));
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: VerifyTime: %s"), *FPlatformTime::PrettyTime(BuildStats.VerifyTime));
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: CleanUpTime: %s"), *FPlatformTime::PrettyTime(BuildStats.CleanUpTime));
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: ProcessExecuteTime: %s"), *FPlatformTime::PrettyTime(BuildStats.ProcessExecuteTime));
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: ProcessPausedTime: %.1f sec"), BuildStats.ProcessPausedTime);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: ProcessSuccess: %s"), BuildStats.ProcessSuccess ? TEXT("TRUE") : TEXT("FALSE"));
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: FailureReason: %s"), *BuildStats.FailureReason);
        GLog->Logf(TEXT("BuildPatchServices: Build Stat: FailureReasonText: %s"), *BuildStats.FailureReasonText.BuildSourceString());
    }

    // Mark that we are done
    SetRunning(false);

    return bSuccess ? 0 : 1;
}
uint32 FBuildPatchFileConstructor::Run()
{
	SetRunning( true );
	SetInited( true );
	const bool bIsFileData = BuildManifest->IsFileDataManifest();

	// Save the list of completed files
	TArray< FString > ConstructedFiles;

	// Check for resume data
	FResumeData ResumeData( StagingDirectory, BuildManifest );

	// Start resume progress at zero or one
	BuildProgress->SetStateProgress( EBuildPatchProgress::Resuming, ResumeData.bHasResumeData ? 0.0f : 1.0f );

	// While we have files to construct, run
	FString FileToConstruct;
	while( GetFileToConstruct( FileToConstruct ) && !FBuildPatchInstallError::HasFatalError() )
	{
		// Check resume status, currently we are only supporting sequential resume, so once we start downloading, we can't resume any more.
		// this only comes up if the resume data has been changed externally.
		ResumeData.CheckFile( FileToConstruct );
		const bool bFilePreviouslyComplete = !bIsDownloadStarted && ResumeData.FilesCompleted.Contains( FileToConstruct );
		const bool bFilePreviouslyStarted = !bIsDownloadStarted && ResumeData.FilesStarted.Contains( FileToConstruct );

		// Construct or skip the file
		bool bFileSuccess;
		if( bFilePreviouslyComplete )
		{
			bFileSuccess = true;
			CountBytesProcessed( BuildManifest->GetFileSize( FileToConstruct ) );
			// Inform the chunk cache of the chunk skip
			if( !bIsFileData )
			{
				FBuildPatchChunkCache::Get().SkipFile( FileToConstruct );
			}
		}
		else
		{
			bFileSuccess = ConstructFileFromChunks( FileToConstruct, bFilePreviouslyStarted );
			// Add to resume data if successful or failure was not file construction fail
			if( bFileSuccess || FBuildPatchInstallError::GetErrorState() != EBuildPatchInstallError::FileConstructionFail )
			{
				ResumeData.FilesStarted.AddUnique( FileToConstruct );
			}
		}

		// If the file succeeded, add to lists
		if( bFileSuccess )
		{
			ConstructedFiles.Add( FileToConstruct );
		}
		else
		{
			GWarn->Logf( TEXT( "BuildPatchServices: ERROR: Failed to construct file %s" ), *FPaths::GetCleanFilename( FileToConstruct ) );
			FBuildPatchInstallError::SetFatalError( EBuildPatchInstallError::FileConstructionFail );
		}

		// Pause
		BuildProgress->WaitWhilePaused();
	}

	// Save resume data
	ResumeData.SaveOut();
	BuildProgress->SetStateProgress(EBuildPatchProgress::Resuming, 1.0f);

	// Set constructed files
	ThreadLock.Lock();
	FilesConstructed.Empty();
	FilesConstructed.Append( ConstructedFiles );
	ThreadLock.Unlock();

	SetRunning( false );
	return 0;
}