Skip to content

Commit

Permalink
Merge pull request #94 from PlasticSCM/1002462-ongoing-workspace-crea…
Browse files Browse the repository at this point in the history
…tion

Fix ongoing workspace creation shouldn't be interrupted if the users close the window too early
  • Loading branch information
SRombautsU authored Oct 18, 2023
2 parents 5da1192 + c5a92d1 commit 769ead3
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 245 deletions.
37 changes: 21 additions & 16 deletions Source/PlasticSourceControl/Private/PlasticSourceControlMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ bool FPlasticSourceControlMenu::CanRemoveLocks(TArray<FAssetData> InAssetObjectP
{
const FString AbsoluteFilename = FPaths::ConvertRelativePathToFull(File);
const auto State = FPlasticSourceControlModule::Get().GetProvider().GetStateInternal(AbsoluteFilename);
// If Locked or Retained, the lock can be removed, that is completely deleted in order to simply ignore the changes from the branch
// If Locked or Retained, the lock can be removed, that is completely deleted in order to simply ignore the changes from the branch
if (State->LockedId != ISourceControlState::INVALID_REVISION)
{
return true;
Expand Down Expand Up @@ -345,7 +345,7 @@ void FPlasticSourceControlMenu::SyncProjectClicked()
// Launch a custom "SyncAll" operation
FPlasticSourceControlProvider& Provider = FPlasticSourceControlModule::Get().GetProvider();
TSharedRef<FPlasticSyncAll, ESPMode::ThreadSafe> SyncOperation = ISourceControlOperation::Create<FPlasticSyncAll>();
const ECommandResult::Type Result = Provider.Execute(SyncOperation, TArray<FString>(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateRaw(this, &FPlasticSourceControlMenu::OnSourceControlOperationComplete));
const ECommandResult::Type Result = Provider.Execute(SyncOperation, TArray<FString>(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateRaw(this, &FPlasticSourceControlMenu::OnSyncAllOperationComplete));
if (Result == ECommandResult::Succeeded)
{
// Display an ongoing notification during the whole operation (packages will be reloaded at the completion of the operation)
Expand Down Expand Up @@ -417,7 +417,7 @@ void FPlasticSourceControlMenu::RevertAllClicked()
// Launch a "RevertAll" Operation
FPlasticSourceControlProvider& Provider = FPlasticSourceControlModule::Get().GetProvider();
TSharedRef<FPlasticRevertAll, ESPMode::ThreadSafe> RevertAllOperation = ISourceControlOperation::Create<FPlasticRevertAll>();
const ECommandResult::Type Result = Provider.Execute(RevertAllOperation, TArray<FString>(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateRaw(this, &FPlasticSourceControlMenu::OnSourceControlOperationComplete));
const ECommandResult::Type Result = Provider.Execute(RevertAllOperation, TArray<FString>(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateRaw(this, &FPlasticSourceControlMenu::OnRevertAllOperationComplete));
if (Result == ECommandResult::Succeeded)
{
// Display an ongoing notification during the whole operation
Expand Down Expand Up @@ -624,23 +624,28 @@ void FPlasticSourceControlMenu::DisplayFailureNotification(const FName& InOperat
UE_LOG(LogSourceControl, Error, TEXT("%s"), *NotificationText.ToString());
}

void FPlasticSourceControlMenu::OnSyncAllOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult)
{
OnSourceControlOperationComplete(InOperation, InResult);

// Reload packages that where updated by the Sync operation (and the current map if needed)
TSharedRef<FPlasticSyncAll, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FPlasticSyncAll>(InOperation);
PackageUtils::ReloadPackages(Operation->UpdatedFiles);
}

void FPlasticSourceControlMenu::OnRevertAllOperationComplete(const FSourceControlOperationRef & InOperation, ECommandResult::Type InResult)
{
OnSourceControlOperationComplete(InOperation, InResult);

// Reload packages that where updated by the Revert operation (and the current map if needed)
TSharedRef<FPlasticRevertAll, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FPlasticRevertAll>(InOperation);
PackageUtils::ReloadPackages(Operation->UpdatedFiles);
}

void FPlasticSourceControlMenu::OnSourceControlOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult)
{
RemoveInProgressNotification();

if (InOperation->GetName() == "SyncAll")
{
// Reload packages that where updated by the Sync operation (and the current map if needed)
TSharedRef<FPlasticSyncAll, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FPlasticSyncAll>(InOperation);
PackageUtils::ReloadPackages(Operation->UpdatedFiles);
}
else if (InOperation->GetName() == "RevertAll")
{
// Reload packages that where updated by the Revert operation (and the current map if needed)
TSharedRef<FPlasticRevertAll, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FPlasticRevertAll>(InOperation);
PackageUtils::ReloadPackages(Operation->UpdatedFiles);
}

// Report result with a notification
if (InResult == ECommandResult::Succeeded)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ class FPlasticSourceControlMenu
/** Name of the asset context menu extension for admin actions over Locks */
static FName UnityVersionControlAssetContextLocksMenuOwnerName;

/** Delegate called when a source control operation has completed */
/** Delegates called when a source control operation has completed */
void OnSyncAllOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult);
void OnRevertAllOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult);
/** Generic delegate and notification handler */
void OnSourceControlOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult);
};
10 changes: 10 additions & 0 deletions Source/PlasticSourceControl/Private/PlasticSourceControlModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
#include "PlasticSourceControlProvider.h"
#include "PlasticSourceControlWorkspaceCreation.h"

/**
* PlasticSourceControl is the official Unity Version Control Plugin for Unreal Engine
Expand All @@ -29,6 +30,12 @@ class FPlasticSourceControlModule : public IModuleInterface
return PlasticSourceControlProvider;
}

/** Access the controller to create a new workspace */
FPlasticSourceControlWorkspaceCreation& GetWorkspaceCreation()
{
return PlasticSourceControlWorkspaceCreation;
}

/**
* Singleton-like access to this module's interface. This is just for convenience!
* Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
Expand All @@ -51,4 +58,7 @@ class FPlasticSourceControlModule : public IModuleInterface
private:
/** The Plastic source control provider */
FPlasticSourceControlProvider PlasticSourceControlProvider;

/** Logic to create a new workspace */
FPlasticSourceControlWorkspaceCreation PlasticSourceControlWorkspaceCreation;
};
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ FName FPlasticMakeWorkspace::GetName() const

FText FPlasticMakeWorkspace::GetInProgressString() const
{
return LOCTEXT("SourceControl_MakeWorkspace", "Creating a new Repository and Workspace");
return LOCTEXT("SourceControl_MakeWorkspace", "Creating a new Repository and Workspace...");
}

FName FPlasticSwitchToPartialWorkspace::GetName() const
Expand All @@ -115,7 +115,7 @@ FName FPlasticSwitchToPartialWorkspace::GetName() const

FText FPlasticSwitchToPartialWorkspace::GetInProgressString() const
{
return LOCTEXT("SourceControl_SwitchToPartialWorkspace", "Switching to a Partial/Gluon Workspace");
return LOCTEXT("SourceControl_SwitchToPartialWorkspace", "Switching to a Partial/Gluon Workspace...");
}

FName FPlasticUnlock::GetName() const
Expand All @@ -126,9 +126,9 @@ FName FPlasticUnlock::GetName() const
FText FPlasticUnlock::GetInProgressString() const
{
if (bRemove)
return LOCTEXT("SourceControl_Unlock_Remove", "Removing Lock(s)");
return LOCTEXT("SourceControl_Unlock_Remove", "Removing Lock(s)...");
else
return LOCTEXT("SourceControl_Unlock_Release", "Releasing Lock(s)");
return LOCTEXT("SourceControl_Unlock_Release", "Releasing Lock(s)...");
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include "PlasticSourceControlModule.h"
#include "PlasticSourceControlState.h"
#include "PlasticSourceControlUtils.h"
#include "SPlasticSourceControlSettings.h"

#include "HAL/FileManager.h"
#include "Misc/Paths.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "CoreMinimal.h"
#include "ISourceControlRevision.h"
#include "ISourceControlState.h"
#include "Runtime/Launch/Resources/Version.h"

class FPlasticSourceControlState;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// Copyright Unity Technologies

#include "PlasticSourceControlWorkspaceCreation.h"

#include "PlasticSourceControlOperations.h"
#include "PlasticSourceControlModule.h"

#include "SourceControlOperations.h"
#include "ISourceControlModule.h"
#include "Misc/Paths.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"

#define LOCTEXT_NAMESPACE "FPlasticSourceControlWorkspaceCreation"


void FPlasticSourceControlWorkspaceCreation::MakeWorkspace(const FParameters& InParameters)
{
WorkspaceParams = InParameters;

// 1.a. Create a repository (if not already existing) and a workspace: launch an asynchronous MakeWorkspace operation
LaunchMakeWorkspaceOperation();
}

/// 1. Create a repository (if not already existing) and a workspace
void FPlasticSourceControlWorkspaceCreation::LaunchMakeWorkspaceOperation()
{
TSharedRef<FPlasticMakeWorkspace, ESPMode::ThreadSafe> MakeWorkspaceOperation = ISourceControlOperation::Create<FPlasticMakeWorkspace>();
MakeWorkspaceOperation->WorkspaceName = WorkspaceParams.WorkspaceName.ToString();
MakeWorkspaceOperation->RepositoryName = WorkspaceParams.RepositoryName.ToString();
MakeWorkspaceOperation->ServerUrl = WorkspaceParams.ServerUrl.ToString();
MakeWorkspaceOperation->bPartialWorkspace = WorkspaceParams.bCreatePartialWorkspace;

FPlasticSourceControlProvider& Provider = FPlasticSourceControlModule::Get().GetProvider();
ECommandResult::Type Result = Provider.Execute(MakeWorkspaceOperation, TArray<FString>(), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateRaw(this, &FPlasticSourceControlWorkspaceCreation::OnMakeWorkspaceOperationComplete));
if (Result == ECommandResult::Succeeded)
{
DisplayInProgressNotification(MakeWorkspaceOperation->GetInProgressString());
}
else
{
DisplayFailureNotification(MakeWorkspaceOperation->GetName());
}
}

void FPlasticSourceControlWorkspaceCreation::OnMakeWorkspaceOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult)
{
OnSourceControlOperationComplete(InOperation, InResult);

// Launch the next asynchronous operation
LaunchMarkForAddOperation();
}

/// 2. Add all project files to Source Control (.uproject, Config/, Content/, Source/ files and ignore.conf if any)
void FPlasticSourceControlWorkspaceCreation::LaunchMarkForAddOperation()
{
TSharedRef<FMarkForAdd, ESPMode::ThreadSafe> MarkForAddOperation = ISourceControlOperation::Create<FMarkForAdd>();
FPlasticSourceControlProvider& Provider = FPlasticSourceControlModule::Get().GetProvider();

// 1.b. Check the new workspace status to enable connection
Provider.CheckPlasticAvailability();

if (Provider.IsWorkspaceFound())
{
// 2. Add all project files to Source Control (.uproject, Config/, Content/, Source/ files and ignore.conf if any)
const TArray<FString> ProjectFiles = GetProjectFiles();
ECommandResult::Type Result = Provider.Execute(MarkForAddOperation, ProjectFiles, EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateRaw(this, &FPlasticSourceControlWorkspaceCreation::OnMarkForAddOperationComplete));
if (Result == ECommandResult::Succeeded)
{
DisplayInProgressNotification(MarkForAddOperation->GetInProgressString());
}
else
{
DisplayFailureNotification(MarkForAddOperation->GetName());
}
}
else
{
DisplayFailureNotification(MarkForAddOperation->GetName());
}
}

void FPlasticSourceControlWorkspaceCreation::OnMarkForAddOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult)
{
OnSourceControlOperationComplete(InOperation, InResult);

// Launch the next asynchronous operation
LaunchCheckInOperation();
}

/// 3. Launch an asynchronous "CheckIn" operation and start another ongoing notification
void FPlasticSourceControlWorkspaceCreation::LaunchCheckInOperation()
{
TSharedRef<FCheckIn, ESPMode::ThreadSafe> CheckInOperation = ISourceControlOperation::Create<FCheckIn>();
CheckInOperation->SetDescription(WorkspaceParams.InitialCommitMessage);
FPlasticSourceControlProvider& Provider = FPlasticSourceControlModule::Get().GetProvider();
const TArray<FString> ProjectFiles = GetProjectFiles(); // Note: listing files and folders is only needed for the update status operation following the checkin to know on what to operate
ECommandResult::Type Result = Provider.Execute(CheckInOperation, ProjectFiles, EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateRaw(this, &FPlasticSourceControlWorkspaceCreation::OnCheckInOperationComplete));
if (Result == ECommandResult::Succeeded)
{
DisplayInProgressNotification(CheckInOperation->GetInProgressString());
}
else
{
DisplayFailureNotification(CheckInOperation->GetName());
}
}

void FPlasticSourceControlWorkspaceCreation::OnCheckInOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult)
{
OnSourceControlOperationComplete(InOperation, InResult);

// Note: no more operation to launch, the workspace is ready to use
}

void FPlasticSourceControlWorkspaceCreation::OnSourceControlOperationComplete(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult)
{
RemoveInProgressNotification();

// Report result with a notification
if (InResult == ECommandResult::Succeeded)
{
DisplaySuccessNotification(InOperation->GetName());
}
else
{
DisplayFailureNotification(InOperation->GetName());
}
}

// Display an ongoing notification during the whole operation
void FPlasticSourceControlWorkspaceCreation::DisplayInProgressNotification(const FText& InOperationInProgressString)
{
if (!OperationInProgressNotification.IsValid())
{
FNotificationInfo Info(InOperationInProgressString);
Info.bFireAndForget = false;
Info.ExpireDuration = 0.0f;
Info.FadeOutDuration = 1.0f;
OperationInProgressNotification = FSlateNotificationManager::Get().AddNotification(Info);
if (OperationInProgressNotification.IsValid())
{
OperationInProgressNotification.Pin()->SetCompletionState(SNotificationItem::CS_Pending);
}
}
}

// Remove the ongoing notification at the end of the operation
void FPlasticSourceControlWorkspaceCreation::RemoveInProgressNotification()
{
if (OperationInProgressNotification.IsValid())
{
OperationInProgressNotification.Pin()->ExpireAndFadeout();
OperationInProgressNotification.Reset();
}
}

// Display a temporary success notification at the end of the operation
void FPlasticSourceControlWorkspaceCreation::DisplaySuccessNotification(const FName& InOperationName)
{
const FText NotificationText = FText::Format(LOCTEXT("InitWorkspace_Success", "{0} operation was successful!"), FText::FromName(InOperationName));
FNotificationInfo Info(NotificationText);
Info.bUseSuccessFailIcons = true;
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
Info.Image = FAppStyle::GetBrush(TEXT("NotificationList.SuccessImage"));
#else
Info.Image = FEditorStyle::GetBrush(TEXT("NotificationList.SuccessImage"));
#endif
FSlateNotificationManager::Get().AddNotification(Info);
UE_LOG(LogSourceControl, Verbose, TEXT("%s"), *NotificationText.ToString());
}

// Display a temporary failure notification at the end of the operation
void FPlasticSourceControlWorkspaceCreation::DisplayFailureNotification(const FName& InOperationName)
{
const FText NotificationText = FText::Format(LOCTEXT("InitWorkspace_Failure", "Error: {0} operation failed!"), FText::FromName(InOperationName));
FNotificationInfo Info(NotificationText);
Info.ExpireDuration = 8.0f;
FSlateNotificationManager::Get().AddNotification(Info);
UE_LOG(LogSourceControl, Error, TEXT("%s"), *NotificationText.ToString());
}

/** Path to the "ignore.conf" file */
const FString FPlasticSourceControlWorkspaceCreation::GetIgnoreFileName() const
{
const FString PathToWorkspaceRoot = FPlasticSourceControlModule::Get().GetProvider().GetPathToWorkspaceRoot();
const FString IgnoreFileName = FPaths::Combine(*PathToWorkspaceRoot, TEXT("ignore.conf"));
return IgnoreFileName;
}

/** List of files to add to Source Control (.uproject, Config/, Content/, Source/ files and ignore.conf if any) */
TArray<FString> FPlasticSourceControlWorkspaceCreation::GetProjectFiles() const
{
TArray<FString> ProjectFiles;
ProjectFiles.Add(FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath()));
ProjectFiles.Add(FPaths::ConvertRelativePathToFull(FPaths::ProjectConfigDir()));
ProjectFiles.Add(FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir()));
if (FPaths::DirectoryExists(FPaths::GameSourceDir()))
{
ProjectFiles.Add(FPaths::ConvertRelativePathToFull(FPaths::GameSourceDir()));
}
if (FPaths::FileExists(GetIgnoreFileName()))
{
ProjectFiles.Add(GetIgnoreFileName());
}
return ProjectFiles;
}

#undef LOCTEXT_NAMESPACE
Loading

0 comments on commit 769ead3

Please sign in to comment.