Jelajahi Sumber

Fixes the bug causing asset browser to be unable to delete/move/etc. (#17568)

* Fixes #15671 - [Bug Report] Asset Browser cannot delete asset
* Adds missing functionality from the Linux ProcessWatcher to indicate
  the difference between a different problem launching program, and
  the program not being found.
* Fixes the workflow of the Move/Delete/Rename operations in the Asset
  Browser to properly return the appropriate error when Source Control
  is not properly configured.
* Adds a toggle for source control notification in AP, so that if you
  toggle the Editor to not use Source Control, the AP will not either.
* Uses the same setting in AP as the Editor, to determine whether Source
  Control should be used or not, and sets it to false by default, just
  like the Editor.

Signed-off-by: Nicholas Lawson <[email protected]>
Nicholas Lawson 1 tahun lalu
induk
melakukan
40cde772e6
18 mengubah file dengan 440 tambahan dan 108 penghapusan
  1. 31 2
      Code/Editor/MainStatusBar.cpp
  2. 1 1
      Code/Editor/Plugins/PerforcePlugin/Platform/Linux/PAL_linux.cmake
  3. 25 3
      Code/Framework/AzFramework/AzFramework/Asset/AssetProcessorMessages.cpp
  4. 19 1
      Code/Framework/AzFramework/AzFramework/Asset/AssetProcessorMessages.h
  5. 1 0
      Code/Framework/AzFramework/AzFramework/Asset/AssetSystemBus.h
  6. 1 0
      Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponent.cpp
  7. 1 0
      Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponent.h
  8. 7 0
      Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponentHelper.cpp
  9. 11 1
      Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp
  10. 14 12
      Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/AssetFolderThumbnailView.cpp
  11. 3 0
      Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/AssetFolderThumbnailView.h
  12. 176 42
      Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserViewUtils.cpp
  13. 14 5
      Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.cpp
  14. 69 34
      Code/Tools/AssetProcessor/native/AssetManager/AssetRequestHandler.cpp
  15. 21 0
      Code/Tools/AssetProcessor/native/AssetManager/SourceFileRelocator.cpp
  16. 7 0
      Code/Tools/AssetProcessor/native/connection/connectionManager.h
  17. 38 7
      Code/Tools/AssetProcessor/native/utilities/GUIApplicationManager.cpp
  18. 1 0
      Gems/EMotionFX/Code/Tests/Editor/MotionSetLoadEscalation.cpp

+ 31 - 2
Code/Editor/MainStatusBar.cpp

@@ -12,6 +12,8 @@
 #include "MainStatusBar.h"
 #include "MainStatusBar.h"
 
 
 #include <AzCore/Utils/Utils.h>
 #include <AzCore/Utils/Utils.h>
+#include <AzFramework/Asset/AssetSystemBus.h>
+
 // AzQtComponents
 // AzQtComponents
 #include <AzQtComponents/Components/Widgets/CheckBox.h>
 #include <AzQtComponents/Components/Widgets/CheckBox.h>
 #include <AzQtComponents/Components/Style.h>
 #include <AzQtComponents/Components/Style.h>
@@ -305,8 +307,21 @@ void SourceControlItem::UpdateAndShowMenu()
 
 
 void SourceControlItem::ConnectivityStateChanged(const AzToolsFramework::SourceControlState state)
 void SourceControlItem::ConnectivityStateChanged(const AzToolsFramework::SourceControlState state)
 {
 {
+    AzToolsFramework::SourceControlState oldState = m_SourceControlState;
     m_SourceControlState = state;
     m_SourceControlState = state;
     UpdateMenuItems();
     UpdateMenuItems();
+
+    if (oldState != m_SourceControlState)
+    {
+        // if the user has turned the system on or off, signal the asset processor
+        // we know the user has turned the system off if the old state was disabled
+        // or if the new state is disabled (when the state changed)
+        if ((oldState == AzToolsFramework::SourceControlState::Disabled) || (m_SourceControlState == AzToolsFramework::SourceControlState::Disabled))
+        {
+            bool enabled = m_SourceControlState != AzToolsFramework::SourceControlState::Disabled;
+            AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::UpdateSourceControlStatus, enabled);
+        }
+    }
 }
 }
 
 
 void SourceControlItem::InitMenu()
 void SourceControlItem::InitMenu()
@@ -342,7 +357,19 @@ void SourceControlItem::InitMenu()
 
 
         connect(m_settingsAction, &QAction::triggered, this, [&]()
         connect(m_settingsAction, &QAction::triggered, this, [&]()
         {
         {
-            GetIEditor()->GetSourceControl()->ShowSettings();
+            // LEGACY note - GetIEditor and GetSourceControl are both legacy functions
+            // but are currently the only way to actually show the source control settings.
+            // They come from an editor plugin which should be moved to a gem eventually instead.
+            // For now, the actual function of the p4 plugin (so for example, checking file status, checking out files, etc)
+            // lives in AzToolsFramework, and is always available,
+            // but the settings dialog lives in the plugin, which is not always available, on all platforms, and thus
+            // this pointer could be null despite P4 still functioning.  (For example, it can function via command line on linux,
+            // and its settings can come from the P4 ENV or P4 Client env, without having to show the GUI).  Therefore
+            // it is still valid to have m_sourceControlAvailable be true yet GetIEditor()->GetSourceControl() be null.
+            if (auto sourceControl = GetIEditor()->GetSourceControl())
+            {
+                sourceControl->ShowSettings();
+            }
         });
         });
 
 
         connect(m_checkBox, &QCheckBox::stateChanged, this, [this](int state) {SetSourceControlEnabledState(state); });
         connect(m_checkBox, &QCheckBox::stateChanged, this, [this](int state) {SetSourceControlEnabledState(state); });
@@ -367,6 +394,7 @@ void SourceControlItem::UpdateMenuItems()
     QString toolTip;
     QString toolTip;
     bool disabled = false;
     bool disabled = false;
     bool errorIcon = false;
     bool errorIcon = false;
+    bool invalidConfig = false;
 
 
     switch (m_SourceControlState)
     switch (m_SourceControlState)
     {
     {
@@ -377,6 +405,7 @@ void SourceControlItem::UpdateMenuItems()
         break;
         break;
     case AzToolsFramework::SourceControlState::ConfigurationInvalid:
     case AzToolsFramework::SourceControlState::ConfigurationInvalid:
         errorIcon = true;
         errorIcon = true;
+        invalidConfig = true;
         toolTip = tr("Perforce configuration invalid");
         toolTip = tr("Perforce configuration invalid");
         break;
         break;
     case AzToolsFramework::SourceControlState::Active:
     case AzToolsFramework::SourceControlState::Active:
@@ -386,7 +415,7 @@ void SourceControlItem::UpdateMenuItems()
 
 
     m_settingsAction->setEnabled(!disabled);
     m_settingsAction->setEnabled(!disabled);
     m_checkBox->setChecked(!disabled);
     m_checkBox->setChecked(!disabled);
-    m_checkBox->setText(disabled ? tr("Status: Offline") : tr("Status: Online"));
+    m_checkBox->setText(disabled ? tr("Status: Offline") : invalidConfig ? tr("Status: Invalid Configuration - check the console log") : tr("Status: Online"));
     SetIcon(errorIcon ? disabled ? m_scIconDisabled : m_scIconWarning : m_scIconOk);
     SetIcon(errorIcon ? disabled ? m_scIconDisabled : m_scIconWarning : m_scIconOk);
     SetToolTip(toolTip);
     SetToolTip(toolTip);
 }
 }

+ 1 - 1
Code/Editor/Plugins/PerforcePlugin/Platform/Linux/PAL_linux.cmake

@@ -6,4 +6,4 @@
 #
 #
 #
 #
 
 
-set(PAL_TRAIT_BUILD_P4PLUGIN_SUPPORTED FALSE)
+set(PAL_TRAIT_BUILD_P4PLUGIN_SUPPORTED TRUE)

+ 25 - 3
Code/Framework/AzFramework/AzFramework/Asset/AssetProcessorMessages.cpp

@@ -621,6 +621,26 @@ namespace AzFramework
 
 
         //---------------------------------------------------------------------
         //---------------------------------------------------------------------
 
 
+        unsigned int UpdateSourceControlStatusRequest::GetMessageType() const
+        {
+            return MessageType;
+        }
+
+        void UpdateSourceControlStatusRequest::Reflect(AZ::ReflectContext* context)
+        {
+            auto serialize = azrtti_cast<AZ::SerializeContext*>(context);
+            if (serialize)
+            {
+                serialize->Class<UpdateSourceControlStatusRequest>()
+                    ->Version(1)
+                    ->Field("SourceControlEnabled", &UpdateSourceControlStatusRequest::m_sourceControlEnabled);
+            }
+        }
+
+        //---------------------------------------------------------------------
+
+        //---------------------------------------------------------------------
+
         unsigned int ShowAssetInAssetProcessorRequest::GetMessageType() const
         unsigned int ShowAssetInAssetProcessorRequest::GetMessageType() const
         {
         {
             return MessageType;
             return MessageType;
@@ -1543,8 +1563,9 @@ namespace AzFramework
             }
             }
         }
         }
 
 
-        AssetChangeReportResponse::AssetChangeReportResponse(AZStd::vector<AZStd::string> lines)
+        AssetChangeReportResponse::AssetChangeReportResponse(AZStd::vector<AZStd::string> lines, bool success)
             : m_lines(lines)
             : m_lines(lines)
+            , m_success(success)
         {
         {
         }
         }
 
 
@@ -1559,8 +1580,9 @@ namespace AzFramework
             if (serialize)
             if (serialize)
             {
             {
                 serialize->Class<AssetChangeReportResponse, BaseAssetProcessorMessage>()
                 serialize->Class<AssetChangeReportResponse, BaseAssetProcessorMessage>()
-                    ->Version(1)
-                    ->Field("Report", &AssetChangeReportResponse::m_lines);
+                    ->Version(2)
+                    ->Field("Report", &AssetChangeReportResponse::m_lines)
+                    ->Field("Success", &AssetChangeReportResponse::m_success);
             }
             }
         }
         }
 
 

+ 19 - 1
Code/Framework/AzFramework/AzFramework/Asset/AssetProcessorMessages.h

@@ -573,6 +573,23 @@ namespace AzFramework
             unsigned int GetMessageType() const override;
             unsigned int GetMessageType() const override;
         };
         };
 
 
+        //! Sent from any tool to the AP, notifying it to toggle the state of source control to either on or off.
+        //! This avoids the need for AP to restart.
+        class UpdateSourceControlStatusRequest
+            : public BaseAssetProcessorMessage
+        {
+        public:
+            AZ_CLASS_ALLOCATOR(UpdateSourceControlStatusRequest, AZ::OSAllocator);
+            AZ_RTTI(UpdateSourceControlStatusRequest, "{B313400A-3E5D-496F-BD91-09B9C10EBDF0}", BaseAssetProcessorMessage);
+            static void Reflect(AZ::ReflectContext* context);
+            static constexpr unsigned int MessageType = AZ_CRC("AssetSystem::UpdateSourceControlStatusRequest", 0x42f7a516);
+
+            UpdateSourceControlStatusRequest() = default;
+            unsigned int GetMessageType() const override;
+
+            bool m_sourceControlEnabled = false;
+        };
+
         //////////////////////////////////////////////////////////////////////////
         //////////////////////////////////////////////////////////////////////////
         //ShowAssetInAssetProcessorRequest
         //ShowAssetInAssetProcessorRequest
         class ShowAssetInAssetProcessorRequest
         class ShowAssetInAssetProcessorRequest
@@ -1310,10 +1327,11 @@ namespace AzFramework
 
 
             // The default constructor is only required for the SerializeContext.
             // The default constructor is only required for the SerializeContext.
             AssetChangeReportResponse() = default;
             AssetChangeReportResponse() = default;
-            AssetChangeReportResponse(AZStd::vector<AZStd::string> lines);
+            AssetChangeReportResponse(AZStd::vector<AZStd::string> lines, bool success);
             unsigned int GetMessageType() const override;
             unsigned int GetMessageType() const override;
 
 
             AZStd::vector<AZStd::string> m_lines;
             AZStd::vector<AZStd::string> m_lines;
+            bool m_success = false;
         };
         };
 
 
     } // namespace AssetSystem
     } // namespace AssetSystem

+ 1 - 0
Code/Framework/AzFramework/AzFramework/Asset/AssetSystemBus.h

@@ -268,6 +268,7 @@ namespace AzFramework
 
 
             //! Show the AssetProcessor App
             //! Show the AssetProcessor App
             virtual void ShowAssetProcessor() = 0;
             virtual void ShowAssetProcessor() = 0;
+            virtual void UpdateSourceControlStatus(bool newStatus) = 0;
             //! Show an asset in the AssetProcessor App
             //! Show an asset in the AssetProcessor App
             virtual void ShowInAssetProcessor(const AZStd::string& assetPath) = 0;
             virtual void ShowInAssetProcessor(const AZStd::string& assetPath) = 0;
 
 

+ 1 - 0
Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponent.cpp

@@ -264,6 +264,7 @@ namespace AzFramework
             RegisterSourceAssetRequest::Reflect(context);
             RegisterSourceAssetRequest::Reflect(context);
             UnregisterSourceAssetRequest::Reflect(context);
             UnregisterSourceAssetRequest::Reflect(context);
             ShowAssetProcessorRequest::Reflect(context);
             ShowAssetProcessorRequest::Reflect(context);
+            UpdateSourceControlStatusRequest::Reflect(context);
             ShowAssetInAssetProcessorRequest::Reflect(context);
             ShowAssetInAssetProcessorRequest::Reflect(context);
             FileOpenRequest::Reflect(context);
             FileOpenRequest::Reflect(context);
             FileCloseRequest::Reflect(context);
             FileCloseRequest::Reflect(context);

+ 1 - 0
Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponent.h

@@ -147,6 +147,7 @@ namespace AzFramework
             bool EscalateAssetBySearchTerm(AZStd::string_view searchTerm) override;
             bool EscalateAssetBySearchTerm(AZStd::string_view searchTerm) override;
 
 
             void ShowAssetProcessor() override;
             void ShowAssetProcessor() override;
+            void UpdateSourceControlStatus(bool newStatus) override;
             void ShowInAssetProcessor(const AZStd::string& assetPath) override;
             void ShowInAssetProcessor(const AZStd::string& assetPath) override;
 
 
             void GetUnresolvedProductReferences(AZ::Data::AssetId assetId, AZ::u32& unresolvedAssetIdReferences, AZ::u32& unresolvedPathReferences) override;
             void GetUnresolvedProductReferences(AZ::Data::AssetId assetId, AZ::u32& unresolvedAssetIdReferences, AZ::u32& unresolvedPathReferences) override;

+ 7 - 0
Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponentHelper.cpp

@@ -43,6 +43,13 @@ namespace AzFramework
             SendRequest(request);
             SendRequest(request);
         }
         }
 
 
+        void AssetSystemComponent::UpdateSourceControlStatus(bool newStatus)
+        {
+            UpdateSourceControlStatusRequest request;
+            request.m_sourceControlEnabled = newStatus;
+            SendRequest(request);
+        }
+
         void AssetSystemComponent::ShowInAssetProcessor(const AZStd::string& assetPath)
         void AssetSystemComponent::ShowInAssetProcessor(const AZStd::string& assetPath)
         {
         {
             AllowAssetProcessorToForeground();
             AllowAssetProcessorToForeground();

+ 11 - 1
Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp

@@ -368,6 +368,10 @@ namespace AzFramework
             {
             {
                 AZ_TracePrintf("Process Watcher", "ProcessLauncher::LaunchProcess: Unable to launch process %s : errno = %s\n", commandAndArgs[0], strerror(errorCodeFromChild));
                 AZ_TracePrintf("Process Watcher", "ProcessLauncher::LaunchProcess: Unable to launch process %s : errno = %s\n", commandAndArgs[0], strerror(errorCodeFromChild));
                 processData.m_childProcessIsDone = true;
                 processData.m_childProcessIsDone = true;
+                if (errorCodeFromChild == ENOENT)
+                {
+                    processLaunchInfo.m_launchResult = PLR_MissingFile; // Note for future maintainers, m_launchResult is mutable
+                }
                 child_pid = -1;
                 child_pid = -1;
             }
             }
         }
         }
@@ -382,7 +386,13 @@ namespace AzFramework
         delete [] commandAndArgs;
         delete [] commandAndArgs;
 
 
         // If an error occurs, exit the application.
         // If an error occurs, exit the application.
-        return child_pid >= 0;
+        if (child_pid >= 0)
+        {
+            processLaunchInfo.m_launchResult = PLR_Success; // Note for future maintainers, m_launchResult is mutable
+            return true;
+        }
+        
+        return false;
     }
     }
 
 
     StdProcessCommunicator* ProcessWatcher::CreateStdCommunicator()
     StdProcessCommunicator* ProcessWatcher::CreateStdCommunicator()

+ 14 - 12
Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/AssetFolderThumbnailView.cpp

@@ -275,22 +275,24 @@ namespace AzQtComponents
         QLineEdit* lineEdit = qobject_cast<QLineEdit*>(widget);
         QLineEdit* lineEdit = qobject_cast<QLineEdit*>(widget);
         if (lineEdit)
         if (lineEdit)
         {
         {
-            connect(
-                lineEdit,
-                &QLineEdit::editingFinished,
-                this,
-                [this]()
-                {
-                    auto sendingLineEdit = qobject_cast<QLineEdit*>(sender());
-                    if (sendingLineEdit)
-                    {
-                        emit RenameThumbnail(sendingLineEdit->text());
-                    }
-                });
+            connect(lineEdit, &QLineEdit::editingFinished, this, &AssetFolderThumbnailViewDelegate::editingFinished);
         }
         }
         return widget;
         return widget;
     }
     }
 
 
+    void AssetFolderThumbnailViewDelegate::editingFinished()
+    {
+        if (auto sendingLineEdit = qobject_cast<QLineEdit*>(sender()); sendingLineEdit)
+        {
+            // debounce this call by disconnecting from the signal immediately.
+            // This is because editingFinished is triggered repeatedly, for example,
+            // when the user presses enter but also when the line edit loses focus.  
+            // If the user presses enter, both happen, causing a double send if we don't debounce like this.
+            QObject::disconnect(sendingLineEdit, &QLineEdit::editingFinished, this, &AssetFolderThumbnailViewDelegate::editingFinished); 
+            emit RenameThumbnail(sendingLineEdit->text());
+        }
+    }
+
     void AssetFolderThumbnailViewDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
     void AssetFolderThumbnailViewDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
     {
     {
         if (QLineEdit* lineEdit = qobject_cast<QLineEdit*>(editor))
         if (QLineEdit* lineEdit = qobject_cast<QLineEdit*>(editor))

+ 3 - 0
Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/AssetFolderThumbnailView.h

@@ -190,6 +190,9 @@ namespace AzQtComponents
 
 
     signals:
     signals:
         void RenameThumbnail(const QString& value) const;
         void RenameThumbnail(const QString& value) const;
+    
+    protected Q_SLOTS:
+        void editingFinished();
 
 
     private:
     private:
         AssetFolderThumbnailView::Config m_config;
         AssetFolderThumbnailView::Config m_config;

+ 176 - 42
Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserViewUtils.cpp

@@ -88,24 +88,47 @@ namespace AzToolsFramework
                         AzQtComponents::FixedWidthMessageBox msgBox(
                         AzQtComponents::FixedWidthMessageBox msgBox(
                             600,
                             600,
                             QObject::tr(isFolder ? "Before Rename Folder Information" : "Before Rename Asset Information"),
                             QObject::tr(isFolder ? "Before Rename Folder Information" : "Before Rename Asset Information"),
-                            QObject::tr("The asset you are renaming may be referenced in other assets."),
+                            response.m_success ? QObject::tr("The asset you are renaming may be referenced in other assets.") :
+                                                 QObject::tr("The asset cannot be renamed."),
                             QObject::tr("More information can be found by pressing \"Show Details...\"."),
                             QObject::tr("More information can be found by pressing \"Show Details...\"."),
                             message.c_str(),
                             message.c_str(),
-                            QMessageBox::Warning,
+                            response.m_success ? QMessageBox::Warning : 
+                                                 QMessageBox::Critical,
                             QMessageBox::Cancel,
                             QMessageBox::Cancel,
-                            QMessageBox::Yes,
+                            response.m_success ? QMessageBox::Yes : QMessageBox::Cancel,
                             callingWidget);
                             callingWidget);
-                        auto* renameButton = msgBox.addButton(QObject::tr("Rename"), QMessageBox::YesRole);
+                        
+                        QPushButton* renameButton = nullptr;
+                        if (response.m_success)
+                        {
+                            renameButton = msgBox.addButton(QObject::tr("Rename"), QMessageBox::YesRole);
+                        }
                         msgBox.exec();
                         msgBox.exec();
 
 
-                        if (msgBox.clickedButton() == static_cast<QAbstractButton*>(renameButton))
+                        if (renameButton && (msgBox.clickedButton() == static_cast<QAbstractButton*>(renameButton)))
                         {
                         {
                             return true;
                             return true;
                         }
                         }
                     }
                     }
                     else
                     else
                     {
                     {
-                        return true;
+                        if (!response.m_success) // failed but without any information why
+                        {
+                            AzQtComponents::FixedWidthMessageBox msgBox(
+                            600,
+                            QObject::tr(isFolder ? "Before Rename Folder Information" : "Before Rename Asset Information"),
+                            QObject::tr("The asset cannot be renamed.  Check the console log for additional information."),
+                            QString(),
+                            QString(),
+                            QMessageBox::Critical,
+                            QMessageBox::Ok,
+                            QMessageBox::Ok,
+                            callingWidget);
+                            msgBox.exec();
+                        }
+
+                        // if it succeeded with no information, we don't show any dialog - it means it Just Worked without issue.
+                        return response.m_success;
                     }
                     }
                 }
                 }
             }
             }
@@ -151,7 +174,7 @@ namespace AzToolsFramework
                 toPath.ReplaceFilename(newVal.toStdString().c_str());
                 toPath.ReplaceFilename(newVal.toStdString().c_str());
                 toPath.ReplaceExtension(extension);
                 toPath.ReplaceExtension(extension);
             }
             }
-            // if the source path is the same as the destintion path then we don't need to go any further
+            // if the source path is the same as the destination path then we don't need to go any further
             if (fromPath == toPath)
             if (fromPath == toPath)
             {
             {
                 return;
                 return;
@@ -187,15 +210,35 @@ namespace AzToolsFramework
                     AzQtComponents::FixedWidthMessageBox msgBox(
                     AzQtComponents::FixedWidthMessageBox msgBox(
                         600,
                         600,
                         QObject::tr(isFolder ? "After Rename Folder Information" : "After Rename Asset Information"),
                         QObject::tr(isFolder ? "After Rename Folder Information" : "After Rename Asset Information"),
-                        QObject::tr("The asset has been renamed."),
+                        moveResponse.m_success ? QObject::tr("The asset has been renamed.") : 
+                                                QObject::tr("The asset cannot be renamed."),
                         QObject::tr("More information can be found by pressing \"Show Details...\"."),
                         QObject::tr("More information can be found by pressing \"Show Details...\"."),
                         message.c_str(),
                         message.c_str(),
-                        QMessageBox::Information,
+                        moveResponse.m_success ? QMessageBox::Information : QMessageBox::Critical,
                         QMessageBox::Ok,
                         QMessageBox::Ok,
                         QMessageBox::Ok,
                         QMessageBox::Ok,
                         callingWidget);
                         callingWidget);
                     msgBox.exec();
                     msgBox.exec();
                 }
                 }
+                else
+                {
+                    // empty response.  It could still have failed or it could have succeeded with no problems at all
+                    if (!moveResponse.m_success)
+                    {
+                        AzQtComponents::FixedWidthMessageBox msgBox(
+                        600,
+                        QObject::tr(isFolder ? "After Rename Folder Information" : "After Rename Asset Information"),
+                        QObject::tr("The asset could not be renamed.  Check the console log for additional information."),
+                        QString(),
+                        QString(),
+                        QMessageBox::Critical,
+                        QMessageBox::Ok,
+                        QMessageBox::Ok,
+                        callingWidget);
+                        msgBox.exec();
+                    }
+                }
+
                 if (isFolder)
                 if (isFolder)
                 {
                 {
                     AZ::IO::SystemFile::DeleteDir(item->GetFullPath().c_str());
                     AZ::IO::SystemFile::DeleteDir(item->GetFullPath().c_str());
@@ -262,7 +305,9 @@ namespace AzToolsFramework
 
 
                     if (SendRequest(request, response))
                     if (SendRequest(request, response))
                     {
                     {
-                        bool canDelete = true;
+                        // when dealing with file deletes, always initialize to the default value of false and only proceed
+                        // when positive confirmation is given by the user.
+                        bool canDelete = false;
 
 
                         if (!response.m_lines.empty())
                         if (!response.m_lines.empty())
                         {
                         {
@@ -271,39 +316,64 @@ namespace AzToolsFramework
                             AzQtComponents::FixedWidthMessageBox msgBox(
                             AzQtComponents::FixedWidthMessageBox msgBox(
                                 600,
                                 600,
                                 QObject::tr(isFolder ? "Before Delete Folder Information" : "Before Delete Asset Information"),
                                 QObject::tr(isFolder ? "Before Delete Folder Information" : "Before Delete Asset Information"),
-                                QObject::tr("The asset you are deleting may be referenced in other assets."),
+                                response.m_success ? QObject::tr("The asset you are deleting may be referenced in other assets.") :
+                                                     QObject::tr("The asset cannot be deleted"),
                                 QObject::tr("More information can be found by pressing \"Show Details...\"."),
                                 QObject::tr("More information can be found by pressing \"Show Details...\"."),
                                 message.c_str(),
                                 message.c_str(),
-                                QMessageBox::Warning,
-                                QMessageBox::Cancel,
-                                QMessageBox::Yes,
+                                response.m_success ? QMessageBox::Warning : QMessageBox::Critical,
+                                QMessageBox::Cancel, // deletion should use cancel as the default operation.
+                                response.m_success ? QMessageBox::Yes : QMessageBox::Cancel,
                                 callingWidget);
                                 callingWidget);
-                            auto* deleteButton = msgBox.addButton(QObject::tr("Delete"), QMessageBox::YesRole);
+                            QPushButton* deleteButton = nullptr;
+                            
+                            if (response.m_success)
+                            {
+                                deleteButton = msgBox.addButton(QObject::tr("Delete"), QMessageBox::YesRole);
+                            }
+
                             msgBox.exec();
                             msgBox.exec();
 
 
-                            if (msgBox.clickedButton() != static_cast<QAbstractButton*>(deleteButton))
+                            if (deleteButton && (msgBox.clickedButton() == static_cast<QAbstractButton*>(deleteButton)))
                             {
                             {
-                                canDelete = false;
+                                canDelete = true;
                             }
                             }
                         }
                         }
                         else
                         else
                         {
                         {
-                            QMessageBox warningBox;
-                            warningBox.setWindowTitle(QObject::tr(isFolder ? "Folder Deletion - Warning" : "Asset Deletion - Warning"));
-                            warningBox.setText(
-                                QObject::tr("O3DE is unable to detect if this file is being used inside a level, prefab, or asset."
-                                            "By deleting these asset(s), you understand this might break a connection if it's still in use."));
-                            warningBox.setInformativeText(
-                                QObject::tr("Currently, delete is a permanent action and cannot be undone.\n"
-                                    "Do you wish to proceed with this deletion?"));
-                            warningBox.setIcon(QMessageBox::Warning);
-                            warningBox.addButton(QMessageBox::Cancel);
-                            warningBox.addButton(QObject::tr("Delete"), QMessageBox::YesRole);
-                            warningBox.setDefaultButton(QMessageBox::Yes);
-                            warningBox.setFixedWidth(600);
-                            if (warningBox.exec() == QMessageBox::Cancel)
+                            if (!response.m_success)
+                            {
+                                AzQtComponents::FixedWidthMessageBox msgBox(
+                                600,
+                                QObject::tr(isFolder ? "Before Delete Folder Information" : "Before Delete Asset Information"),
+                                QObject::tr("The asset cannot be deleted.  Check the console log for additional information."),
+                                QString(),
+                                QString(),
+                                QMessageBox::Critical,
+                                QMessageBox::Ok,
+                                QMessageBox::Ok,
+                                callingWidget);
+                                msgBox.exec();
+                            }
+                            else
                             {
                             {
-                                canDelete = false;
+                                // the response succeeded, but could not determine whether there are dependencies or not.
+                                QMessageBox warningBox;
+                                warningBox.setWindowTitle(QObject::tr(isFolder ? "Folder Deletion - Warning" : "Asset Deletion - Warning"));
+                                warningBox.setText(
+                                    QObject::tr("O3DE is unable to detect if this file is being used inside a level, prefab, or asset."
+                                                "By deleting these asset(s), you understand this might break a connection if it's still in use."));
+                                warningBox.setInformativeText(
+                                    QObject::tr("Currently, delete is a permanent action and cannot be undone.\n"
+                                        "Do you wish to proceed with this deletion?"));
+                                warningBox.setIcon(QMessageBox::Warning);
+                                warningBox.setDefaultButton(warningBox.addButton(QMessageBox::Cancel));
+                                QPushButton* deleteButton = warningBox.addButton(QObject::tr("Delete"), QMessageBox::DestructiveRole);
+                                warningBox.setFixedWidth(600);
+                                warningBox.exec();// allow delete only if affirmation is given.  Assume all other responses are "no".
+                                if (warningBox.clickedButton() == deleteButton)
+                                {
+                                    canDelete = true;
+                                }
                             }
                             }
                         }
                         }
 
 
@@ -321,7 +391,8 @@ namespace AzToolsFramework
                                     AzQtComponents::FixedWidthMessageBox deleteMsgBox(
                                     AzQtComponents::FixedWidthMessageBox deleteMsgBox(
                                         600,
                                         600,
                                         QObject::tr(isFolder ? "After Delete Folder Information" : "After Delete Asset Information"),
                                         QObject::tr(isFolder ? "After Delete Folder Information" : "After Delete Asset Information"),
-                                        QObject::tr("The asset has been deleted."),
+                                        response.m_success ? QObject::tr("The asset has been deleted.") :
+                                                             QObject::tr("Deletion has failed."),
                                         QObject::tr("More information can be found by pressing \"Show Details...\"."),
                                         QObject::tr("More information can be found by pressing \"Show Details...\"."),
                                         deleteMessage.c_str(),
                                         deleteMessage.c_str(),
                                         QMessageBox::Information,
                                         QMessageBox::Information,
@@ -330,7 +401,25 @@ namespace AzToolsFramework
                                         callingWidget);
                                         callingWidget);
                                     deleteMsgBox.exec();
                                     deleteMsgBox.exec();
                                 }
                                 }
+                                else
+                                {
+                                    if (!response.m_success)
+                                    {
+                                        AzQtComponents::FixedWidthMessageBox deleteMsgBox(
+                                            600,
+                                            QObject::tr(isFolder ? "After Delete Folder Information" : "After Delete Asset Information"),
+                                            QObject::tr("The asset could not be deleted.  Check the console log for additional information."),
+                                            QString(),
+                                            QString(),
+                                            QMessageBox::Critical,
+                                            QMessageBox::Ok,
+                                            QMessageBox::Ok,
+                                            callingWidget);
+                                        deleteMsgBox.exec();
+                                    }
+                                }
                             }
                             }
+
                             if (isFolder)
                             if (isFolder)
                             {
                             {
                                 AZ::IO::SystemFile::DeleteDir(item->GetFullPath().c_str());
                                 AZ::IO::SystemFile::DeleteDir(item->GetFullPath().c_str());
@@ -448,30 +537,57 @@ namespace AzToolsFramework
 
 
             if (SendRequest(request, response))
             if (SendRequest(request, response))
             {
             {
-                bool canMove = true;
+                bool canMove = false;
 
 
                 if (!response.m_lines.empty())
                 if (!response.m_lines.empty())
                 {
                 {
+                    // if we get here, it means AP has some information to share with the user about the move operation.
+                    // Note that this could either be a success "you can move it but be aware of these things"
+                    // or it could be a failure "you cannot move this, because of these things"
                     AZStd::string message;
                     AZStd::string message;
                     AZ::StringFunc::Join(message, response.m_lines.begin(), response.m_lines.end(), "\n");
                     AZ::StringFunc::Join(message, response.m_lines.begin(), response.m_lines.end(), "\n");
                     AzQtComponents::FixedWidthMessageBox msgBox(
                     AzQtComponents::FixedWidthMessageBox msgBox(
                         600,
                         600,
                         QObject::tr(isFolder ? "Before Move Folder Information" : "Before Move Asset Information"),
                         QObject::tr(isFolder ? "Before Move Folder Information" : "Before Move Asset Information"),
-                        QObject::tr("The asset you are moving may be referenced in other assets."),
+                        response.m_success ? QObject::tr("The asset you are moving may be referenced in other assets.") :
+                                             QObject::tr("The asset cannot be moved."),
                         QObject::tr("More information can be found by pressing \"Show Details...\"."),
                         QObject::tr("More information can be found by pressing \"Show Details...\"."),
                         message.c_str(),
                         message.c_str(),
-                        QMessageBox::Warning,
+                        response.m_success ? QMessageBox::Warning : QMessageBox::Critical,
                         QMessageBox::Cancel,
                         QMessageBox::Cancel,
-                        QMessageBox::Yes,
+                        response.m_success ? QMessageBox::Yes : QMessageBox::Cancel,
                         parent);
                         parent);
-                    auto* moveButton = msgBox.addButton(QObject::tr("Move"), QMessageBox::YesRole);
+
+                    QPushButton* moveButton = response.m_success ? msgBox.addButton(QObject::tr("Move"), QMessageBox::YesRole) : nullptr;
                     msgBox.exec();
                     msgBox.exec();
 
 
-                    if (msgBox.clickedButton() != static_cast<QAbstractButton*>(moveButton))
+                    if (moveButton && (msgBox.clickedButton() == static_cast<QAbstractButton*>(moveButton)))
                     {
                     {
-                        canMove = false;
+                        canMove = true;
                     }
                     }
                 }
                 }
+                else
+                {
+                    if (!response.m_success) // failed but with no extra info why from AP.
+                    {
+                        AzQtComponents::FixedWidthMessageBox msgBox(
+                            600,
+                            QObject::tr(isFolder ? "Before Move Folder Information" : "Before Move Asset Information"),
+                            QObject::tr("The asset cannot be moved.  Check the console log for additional information."),
+                            QString(),
+                            QString(),
+                            QMessageBox::Critical,
+                            QMessageBox::Ok,
+                            QMessageBox::Ok,
+                            parent);
+                        msgBox.exec();
+                    }
+                    else // succeeded, no objection or extra information from AP.
+                    {
+                        canMove = true;
+                    }
+                }
+
                 if (canMove)
                 if (canMove)
                 {
                 {
                     AssetChangeReportRequest moveRequest(
                     AssetChangeReportRequest moveRequest(
@@ -506,15 +622,33 @@ namespace AzToolsFramework
                             AzQtComponents::FixedWidthMessageBox moveMsgBox(
                             AzQtComponents::FixedWidthMessageBox moveMsgBox(
                                 600,
                                 600,
                                 QObject::tr(isFolder ? "After Move Folder Information" : "After Move Asset Information"),
                                 QObject::tr(isFolder ? "After Move Folder Information" : "After Move Asset Information"),
-                                QObject::tr("The asset has been moved."),
+                                response.m_success ? QObject::tr("The asset has been moved.") :
+                                                     QObject::tr("The asset could not be moved."),
                                 QObject::tr("More information can be found by pressing \"Show Details...\"."),
                                 QObject::tr("More information can be found by pressing \"Show Details...\"."),
                                 moveMessage.c_str(),
                                 moveMessage.c_str(),
-                                QMessageBox::Information,
+                                response.m_success ? QMessageBox::Information : QMessageBox::Critical,
                                 QMessageBox::Ok,
                                 QMessageBox::Ok,
                                 QMessageBox::Ok,
                                 QMessageBox::Ok,
                                 parent);
                                 parent);
                             moveMsgBox.exec();
                             moveMsgBox.exec();
                         }
                         }
+                        else
+                        {
+                            if (!response.m_success) // failed with no reason given
+                            {
+                                AzQtComponents::FixedWidthMessageBox moveMsgBox(
+                                    600,
+                                    QObject::tr(isFolder ? "After Move Folder Information" : "After Move Asset Information"),
+                                    QObject::tr("The asset could not be moved.  Check the console log for additional information."),
+                                    QString(),
+                                    QString(),
+                                    QMessageBox::Critical,
+                                    QMessageBox::Ok,
+                                    QMessageBox::Ok,
+                                    parent);
+                                moveMsgBox.exec();
+                            }
+                        }
                     }
                     }
                     if (isFolder)
                     if (isFolder)
                     {
                     {

+ 14 - 5
Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.cpp

@@ -1036,8 +1036,16 @@ namespace AzToolsFramework
 
 
             if (!p4PortSet)
             if (!p4PortSet)
             {
             {
+                // The warnings need to show up repeatedly, as the user might be turning on and off perforce and configuring it.
+                if (!s_perforceConn->CommandApplicationFound())
+                {
+                    AZ_Warning(SCC_WINDOW, false, "Perforce - p4 executable not found on path, will not use Perforce source control!\n");
+                }
+                else
+                {
+                    AZ_Warning(SCC_WINDOW, false, "Perforce - p4 executable found, but P4PORT (server address) is not set, Perforce not available!\n");
+                }
                 // Disable any further connection status testing
                 // Disable any further connection status testing
-                AZ_WarningOnce(SCC_WINDOW, false, "Perforce - P4PORT (server address) is not set, Perforce not available!\n");
                 m_testTrust = false;
                 m_testTrust = false;
                 m_testConnection = false;
                 m_testConnection = false;
                 m_validConnection = false;
                 m_validConnection = false;
@@ -1138,25 +1146,26 @@ namespace AzToolsFramework
 
 
         if (s_perforceConn->GetUser().empty() || s_perforceConn->GetClientName().empty())
         if (s_perforceConn->GetUser().empty() || s_perforceConn->GetClientName().empty())
         {
         {
-            AZ_WarningOnce(SCC_WINDOW, false, "Perforce - Your client or user is empty, Perforce not available!\n");
+            AZ_Warning(SCC_WINDOW, false, "Perforce - Your client or user is empty, Perforce not available!\n");
             return false;
             return false;
         }
         }
 
 
         if (s_perforceConn->GetClientName().compare("*unknown*") == 0)
         if (s_perforceConn->GetClientName().compare("*unknown*") == 0)
         {
         {
-            AZ_WarningOnce(SCC_WINDOW, false, "Perforce - client spec not found, Perforce not available!\n");
+            AZ_Warning(SCC_WINDOW, false, "Perforce - client spec not found, Perforce not available!\n");
             return false;
             return false;
         }
         }
 
 
         if (s_perforceConn->GetClientRoot().empty())
         if (s_perforceConn->GetClientRoot().empty())
         {
         {
-            AZ_WarningOnce(SCC_WINDOW, false, "Perforce - Your workspace root is empty, Perforce not available!\n");
+            AZ_Warning(SCC_WINDOW, false, "Perforce - Your workspace root is empty, Perforce not available!\n");
             return false;
             return false;
         }
         }
 
 
         if (s_perforceConn->GetServerAddress().empty() || s_perforceConn->GetServerUptime().empty())
         if (s_perforceConn->GetServerAddress().empty() || s_perforceConn->GetServerUptime().empty())
         {
         {
-            AZ_WarningOnce(SCC_WINDOW, false, "Perforce - Could not get server information, Perforce not available!\n");
+            AZ_Warning(SCC_WINDOW, false, "Perforce - Could not get server information, Perforce not available!\n");
+            return false;
         }
         }
 
 
         AZ_TracePrintf(SCC_WINDOW, "Perforce - Connected, User: %s | Client: %s\n", s_perforceConn->GetUser().c_str(), s_perforceConn->GetClientName().c_str());
         AZ_TracePrintf(SCC_WINDOW, "Perforce - Connected, User: %s | Client: %s\n", s_perforceConn->GetUser().c_str(), s_perforceConn->GetClientName().c_str());

+ 69 - 34
Code/Tools/AssetProcessor/native/AssetManager/AssetRequestHandler.cpp

@@ -21,7 +21,8 @@ namespace
     static const uint32_t s_assetPath = AssetUtilities::ComputeCRC32Lowercase("assetPath");
     static const uint32_t s_assetPath = AssetUtilities::ComputeCRC32Lowercase("assetPath");
 }
 }
 
 
-AssetRequestHandler::AssetRequestLine::AssetRequestLine(QString platform, QString searchTerm, const AZ::Data::AssetId& assetId, bool isStatusRequest, int searchType)
+AssetRequestHandler::AssetRequestLine::AssetRequestLine(
+    QString platform, QString searchTerm, const AZ::Data::AssetId& assetId, bool isStatusRequest, int searchType)
     : m_platform(platform)
     : m_platform(platform)
     , m_searchTerm(searchTerm)
     , m_searchTerm(searchTerm)
     , m_isStatusRequest(isStatusRequest)
     , m_isStatusRequest(isStatusRequest)
@@ -73,6 +74,36 @@ namespace
     using namespace AzToolsFramework::AssetSystem;
     using namespace AzToolsFramework::AssetSystem;
     using namespace AzFramework::AssetSystem;
     using namespace AzFramework::AssetSystem;
 
 
+    //! utility function - splits a string into lines and outputs them to the console at the same time as a trace.
+    void ParseToLines(AZStd::vector<AZStd::string>& lines, const AZStd::string& text)
+    {
+        AzFramework::StringFunc::TokenizeVisitor(
+            text,
+            [&lines](AZStd::string line)
+            {
+                lines.push_back(line);
+                AZ::Debug::Trace::Instance().Output(AssetProcessor::ConsoleChannel, (line + "\n").c_str());
+            },
+            "\n");
+    }
+
+    // generic version of BuildFailure, generally assumes that the failure type is a string.
+    template<typename T> 
+    void BuildFailure(T& failure,  AZStd::vector<AZStd::string>& lines)
+    {
+       ParseToLines(lines, failure);
+    }
+
+    // specialized version of BuildFailure, for when the failure type is a MoveFailure, the string will be in m_reason
+    template<> 
+    void BuildFailure(MoveFailure& failure,  AZStd::vector<AZStd::string>& lines)
+    {
+        ParseToLines(lines, failure.m_reason);
+    }
+
+    // Build a report based on the result of an Asset Change Request and echo to the console.
+    // The expected output is a list of strings in the 'lines' variable.
+    // The expected input is a result of an Asset Change Report Request function below
     template<typename T>
     template<typename T>
     void BuildReport(AssetProcessor::ISourceFileRelocation* relocationInterface, T& result, AZStd::vector<AZStd::string>& lines)
     void BuildReport(AssetProcessor::ISourceFileRelocation* relocationInterface, T& result, AZStd::vector<AZStd::string>& lines)
     {
     {
@@ -82,38 +113,37 @@ namespace
 
 
             // The report can be too long for the AZ_Printf buffer, so split it into individual lines
             // The report can be too long for the AZ_Printf buffer, so split it into individual lines
             AZStd::string report = relocationInterface->BuildChangeReport(success.m_relocationContainer, success.m_updateTasks);
             AZStd::string report = relocationInterface->BuildChangeReport(success.m_relocationContainer, success.m_updateTasks);
-            AzFramework::StringFunc::TokenizeVisitor(
-                report,
-                [&lines](AZStd::string line)
-                {
-                    lines.push_back(line);
-                    AZ::Debug::Trace::Instance().Output(AssetProcessor::ConsoleChannel, (line + "\n").c_str());
-                },
-                "\n");
+            ParseToLines(lines, report);
+        }
+        else
+        {
+            BuildFailure(result.GetError(), lines);
         }
         }
     }
     }
 
 
-    AssetChangeReportResponse HandleAssetChangeReportRequest(MessageData < AssetChangeReportRequest> messageData)
+    AssetChangeReportResponse HandleAssetChangeReportRequest(MessageData<AssetChangeReportRequest> messageData)
     {
     {
         AZStd::vector<AZStd::string> lines;
         AZStd::vector<AZStd::string> lines;
+        bool success = false;
 
 
         auto* relocationInterface = AZ::Interface<AssetProcessor::ISourceFileRelocation>::Get();
         auto* relocationInterface = AZ::Interface<AssetProcessor::ISourceFileRelocation>::Get();
         if (relocationInterface)
         if (relocationInterface)
         {
         {
             switch (messageData.m_message->m_type)
             switch (messageData.m_message->m_type)
             {
             {
-                case AssetChangeReportRequest::ChangeType::CheckMove:
+            case AssetChangeReportRequest::ChangeType::CheckMove:
                 {
                 {
-                        auto resultCheck = relocationInterface->Move(
-                            messageData.m_message->m_fromPath,
-                            messageData.m_message->m_toPath,
-                            RelocationParameters_PreviewOnlyFlag | RelocationParameters_AllowDependencyBreakingFlag |
-                                RelocationParameters_UpdateReferencesFlag | RelocationParameters_AllowNonDatabaseFilesFlag);
+                    auto resultCheck = relocationInterface->Move(
+                        messageData.m_message->m_fromPath,
+                        messageData.m_message->m_toPath,
+                        RelocationParameters_PreviewOnlyFlag | RelocationParameters_AllowDependencyBreakingFlag |
+                            RelocationParameters_UpdateReferencesFlag | RelocationParameters_AllowNonDatabaseFilesFlag);
 
 
                     BuildReport(relocationInterface, resultCheck, lines);
                     BuildReport(relocationInterface, resultCheck, lines);
+                    success = resultCheck.IsSuccess();
                     break;
                     break;
                 }
                 }
-                case AssetChangeReportRequest::ChangeType::Move:
+            case AssetChangeReportRequest::ChangeType::Move:
                 {
                 {
                     auto* metadataUpdates = AZ::Interface<AssetProcessor::IMetadataUpdates>::Get();
                     auto* metadataUpdates = AZ::Interface<AssetProcessor::IMetadataUpdates>::Get();
                     AZ_Assert(metadataUpdates, "Programmer Error - IMetadataUpdates interface is not available.");
                     AZ_Assert(metadataUpdates, "Programmer Error - IMetadataUpdates interface is not available.");
@@ -127,11 +157,13 @@ namespace
                             RelocationParameters_AllowNonDatabaseFilesFlag);
                             RelocationParameters_AllowNonDatabaseFilesFlag);
 
 
                     BuildReport(relocationInterface, resultMove, lines);
                     BuildReport(relocationInterface, resultMove, lines);
+                    success = resultMove.IsSuccess();
                     break;
                     break;
                 }
                 }
-                case AssetChangeReportRequest::ChangeType::CheckDelete:
+            case AssetChangeReportRequest::ChangeType::CheckDelete:
                 {
                 {
-                    auto flags = RelocationParameters_PreviewOnlyFlag | RelocationParameters_AllowDependencyBreakingFlag | RelocationParameters_AllowNonDatabaseFilesFlag;
+                    auto flags = RelocationParameters_PreviewOnlyFlag | RelocationParameters_AllowDependencyBreakingFlag |
+                        RelocationParameters_AllowNonDatabaseFilesFlag;
                     if (messageData.m_message->m_isFolder)
                     if (messageData.m_message->m_isFolder)
                     {
                     {
                         flags |= RelocationParameters_RemoveEmptyFoldersFlag;
                         flags |= RelocationParameters_RemoveEmptyFoldersFlag;
@@ -139,9 +171,10 @@ namespace
                     auto resultCheck = relocationInterface->Delete(messageData.m_message->m_fromPath, flags);
                     auto resultCheck = relocationInterface->Delete(messageData.m_message->m_fromPath, flags);
 
 
                     BuildReport(relocationInterface, resultCheck, lines);
                     BuildReport(relocationInterface, resultCheck, lines);
+                    success = resultCheck.IsSuccess();
                     break;
                     break;
                 }
                 }
-                case AssetChangeReportRequest::ChangeType::Delete:
+            case AssetChangeReportRequest::ChangeType::Delete:
                 {
                 {
                     int flags = RelocationParameters_AllowDependencyBreakingFlag | RelocationParameters_AllowNonDatabaseFilesFlag;
                     int flags = RelocationParameters_AllowDependencyBreakingFlag | RelocationParameters_AllowNonDatabaseFilesFlag;
                     if (messageData.m_message->m_isFolder)
                     if (messageData.m_message->m_isFolder)
@@ -151,11 +184,12 @@ namespace
                     auto resultDelete = relocationInterface->Delete(messageData.m_message->m_fromPath, flags);
                     auto resultDelete = relocationInterface->Delete(messageData.m_message->m_fromPath, flags);
 
 
                     BuildReport(relocationInterface, resultDelete, lines);
                     BuildReport(relocationInterface, resultDelete, lines);
+                    success = resultDelete.IsSuccess();
                     break;
                     break;
                 }
                 }
             }
             }
         }
         }
-        return AssetChangeReportResponse(lines);
+        return AssetChangeReportResponse(lines, success);
     }
     }
 
 
     GetFullSourcePathFromRelativeProductPathResponse HandleGetFullSourcePathFromRelativeProductPathRequest(MessageData<GetFullSourcePathFromRelativeProductPathRequest> messageData)
     GetFullSourcePathFromRelativeProductPathResponse HandleGetFullSourcePathFromRelativeProductPathRequest(MessageData<GetFullSourcePathFromRelativeProductPathRequest> messageData)
@@ -330,19 +364,19 @@ namespace
             // Call the appropriate AssetCatalog API based on the type of dependencies requested.
             // Call the appropriate AssetCatalog API based on the type of dependencies requested.
             switch (messageData.m_message->m_dependencyType)
             switch (messageData.m_message->m_dependencyType)
             {
             {
-                case AssetDependencyInfoRequest::DependencyType::DirectDependencies:
-                    AZ::Data::AssetCatalogRequestBus::BroadcastResult(result,
+            case AssetDependencyInfoRequest::DependencyType::DirectDependencies:
+                AZ::Data::AssetCatalogRequestBus::BroadcastResult(result,
                         &AZ::Data::AssetCatalogRequestBus::Events::GetDirectProductDependencies, messageData.m_message->m_assetId);
                         &AZ::Data::AssetCatalogRequestBus::Events::GetDirectProductDependencies, messageData.m_message->m_assetId);
-                    break;
-                case AssetDependencyInfoRequest::DependencyType::AllDependencies:
-                    AZ::Data::AssetCatalogRequestBus::BroadcastResult(result,
+                break;
+            case AssetDependencyInfoRequest::DependencyType::AllDependencies:
+                AZ::Data::AssetCatalogRequestBus::BroadcastResult(result,
                         &AZ::Data::AssetCatalogRequestBus::Events::GetAllProductDependencies, messageData.m_message->m_assetId);
                         &AZ::Data::AssetCatalogRequestBus::Events::GetAllProductDependencies, messageData.m_message->m_assetId);
-                    break;
-                case AssetDependencyInfoRequest::DependencyType::LoadBehaviorDependencies:
-                    AZ::Data::AssetCatalogRequestBus::BroadcastResult(result,
-                        &AZ::Data::AssetCatalogRequestBus::Events::GetLoadBehaviorProductDependencies,
-                        messageData.m_message->m_assetId, response.m_noloadSet, response.m_preloadAssetList);
-                    break;
+                break;
+            case AssetDependencyInfoRequest::DependencyType::LoadBehaviorDependencies:
+                AZ::Data::AssetCatalogRequestBus::BroadcastResult(result,
+                    &AZ::Data::AssetCatalogRequestBus::Events::GetLoadBehaviorProductDependencies,
+                    messageData.m_message->m_assetId, response.m_noloadSet, response.m_preloadAssetList);
+                break;
             }
             }
 
 
             // Decompose the AZ::Outcome into separate variables, since AZ::Outcome is not a serializable type.
             // Decompose the AZ::Outcome into separate variables, since AZ::Outcome is not a serializable type.
@@ -386,8 +420,9 @@ void AssetRequestHandler::HandleRequestEscalateAsset(MessageData<RequestEscalate
 
 
 bool AssetRequestHandler::InvokeHandler(MessageData<AzFramework::AssetSystem::BaseAssetProcessorMessage> messageData)
 bool AssetRequestHandler::InvokeHandler(MessageData<AzFramework::AssetSystem::BaseAssetProcessorMessage> messageData)
 {
 {
-    // This function checks to see whether the incoming message is either one of those request, which require decoding the type of message and then invoking the appropriate EBUS handler.
-    // If the message is not one of those type than it checks to see whether some one has registered a request handler for that message type and then invokes it.
+    // This function checks to see whether the incoming message is either one of those request, which require decoding the type of message
+    // and then invoking the appropriate EBUS handler. If the message is not one of those type than it checks to see whether some one has
+    // registered a request handler for that message type and then invokes it.
 
 
     using namespace AzFramework::AssetSystem;
     using namespace AzFramework::AssetSystem;
 
 

+ 21 - 0
Code/Tools/AssetProcessor/native/AssetManager/SourceFileRelocator.cpp

@@ -21,6 +21,17 @@
 
 
 namespace AssetProcessor
 namespace AssetProcessor
 {
 {
+    // note that this will be true if EITHER the source control is valid, connected, and working
+    // OR if source control is completely disabled and thus will just transparently pass thru to the file system
+    // it will only return false if the source control plugin is ACTIVE but is failing to function due to a configuration
+    // problem, or the executable being missing.
+    bool IsSourceControlValid()
+    {
+        using SCRequest = AzToolsFramework::SourceControlConnectionRequestBus;
+        AzToolsFramework::SourceControlState state = AzToolsFramework::SourceControlState::Disabled;
+        SCRequest::BroadcastResult(state, &SCRequest::Events::GetSourceControlState);
+        return (state != AzToolsFramework::SourceControlState::ConfigurationInvalid);
+    }
 
 
     bool WaitForSourceControl(AZStd::binary_semaphore& waitSignal)
     bool WaitForSourceControl(AZStd::binary_semaphore& waitSignal)
     {
     {
@@ -361,6 +372,16 @@ Please note that only those seed files will get updated that are active for your
         bool excludeMetaDataFiles,
         bool excludeMetaDataFiles,
         bool allowNonDatabaseFiles) const
         bool allowNonDatabaseFiles) const
     {
     {
+        // nothing below will succeed if source control is active but invalid, so early out with a clear
+        // warning to the user.
+        if (!IsSourceControlValid())
+        {
+            // no point in continuing, it will always fail.
+            return AZ::Failure(AZStd::string("The Source Control plugin is active but the configuration is invalid.\n" 
+                                             "Either disable it by right-clicking the source control icon in the editor status bar,\n"
+                                             "or fix the configuration of it in that same right-click menu.\n"));
+        }
+
         if(normalizedSource.find("**") != AZStd::string::npos)
         if(normalizedSource.find("**") != AZStd::string::npos)
         {
         {
             return AZ::Failure(AZStd::string("Consecutive wildcards are not allowed.  Please remove extra wildcards from your query.\n"));
             return AZ::Failure(AZStd::string("Consecutive wildcards are not allowed.  Please remove extra wildcards from your query.\n"));

+ 7 - 0
Code/Tools/AssetProcessor/native/connection/connectionManager.h

@@ -22,6 +22,13 @@
 #endif
 #endif
 
 
 class Connection;
 class Connection;
+//! defines the callback type for registering handlers to handle messages coming from outside into Asset Processor.
+//! params are 
+//! connectionId, who its coming from
+//! messageType, type of message, for use when you bind the same handler interface to many different message types
+//! int serial number of message, to check for duplicates and also if you want to respond, you must respond with the same number
+//! QByteArray payload, the payload of the message
+//! QString platform - the platform of the sender ("pc" for example)
 typedef AZStd::function<void(unsigned int, unsigned int, unsigned int, QByteArray, QString)> regFunc;
 typedef AZStd::function<void(unsigned int, unsigned int, unsigned int, QByteArray, QString)> regFunc;
 typedef QMap<unsigned int, Connection*> ConnectionMap;
 typedef QMap<unsigned int, Connection*> ConnectionMap;
 typedef QMultiMap<unsigned int, regFunc> RouteMultiMap;
 typedef QMultiMap<unsigned int, regFunc> RouteMultiMap;

+ 38 - 7
Code/Tools/AssetProcessor/native/utilities/GUIApplicationManager.cpp

@@ -824,9 +824,17 @@ ApplicationManager::RegistryCheckInstructions GUIApplicationManager::PopupRegist
 void GUIApplicationManager::InitSourceControl()
 void GUIApplicationManager::InitSourceControl()
 {
 {
     // Look in the editor's settings for the Source Control value
     // Look in the editor's settings for the Source Control value
-    QSettings settings(QApplication::organizationName(), QString("O3DE Editor"));
-    settings.beginGroup("Settings");
-    bool enableSourceControl = settings.value("EnableSourceControl", 1).toBool();
+    constexpr AZStd::string_view enableSourceControlKey = "/Amazon/Settings/EnableSourceControl";
+    bool enableSourceControl = false;
+
+    if (const auto* registry = AZ::SettingsRegistry::Get())
+    {
+        bool potentialValue;
+        if (registry->Get(potentialValue, enableSourceControlKey))
+        {
+            enableSourceControl = AZStd::move(potentialValue);
+        }
+    }
 
 
     const AzFramework::CommandLine* commandLine = nullptr;
     const AzFramework::CommandLine* commandLine = nullptr;
     AzFramework::ApplicationRequests::Bus::BroadcastResult(commandLine, &AzFramework::ApplicationRequests::GetCommandLine);
     AzFramework::ApplicationRequests::Bus::BroadcastResult(commandLine, &AzFramework::ApplicationRequests::GetCommandLine);
@@ -836,13 +844,36 @@ void GUIApplicationManager::InitSourceControl()
         enableSourceControl = true;
         enableSourceControl = true;
     }
     }
 
 
-    if (enableSourceControl)
+    AzToolsFramework::SourceControlConnectionRequestBus::Broadcast(&AzToolsFramework::SourceControlConnectionRequestBus::Events::EnableSourceControl, enableSourceControl);
+
+    if (!enableSourceControl)
     {
     {
-        AzToolsFramework::SourceControlConnectionRequestBus::Broadcast(&AzToolsFramework::SourceControlConnectionRequestBus::Events::EnableSourceControl, true);
+        // Source control is disabled, emit the SourceControlReady signal immediately since the source control system will not emit it
+        Q_EMIT SourceControlReady();
     }
     }
-    else
+
+    // Register the source control status request - whenever it comes in, we need to reset our source control
+    // to follow that state:
+    if (m_connectionManager)
     {
     {
-        Q_EMIT SourceControlReady();
+        auto refreshSourceControl = [](unsigned int /*connId*/, unsigned int /*type*/, unsigned int /*serial*/, QByteArray payload, QString /*platform*/)
+        {
+            AzFramework::AssetSystem::UpdateSourceControlStatusRequest request;
+            bool readFromStream = AZ::Utils::LoadObjectFromBufferInPlace(payload.data(), payload.size(), request);
+            AZ_Assert(readFromStream, "GUIApplicationManager::UpdateSourceControlStatusRequest: Could not deserialize from stream");
+            if (readFromStream)
+            {
+                AzToolsFramework::SourceControlState state = AzToolsFramework::SourceControlState::Disabled;
+                AzToolsFramework::SourceControlConnectionRequestBus::BroadcastResult(state, &AzToolsFramework::SourceControlConnectionRequestBus::Events::GetSourceControlState);
+                bool wasEnabled = state != AzToolsFramework::SourceControlState::Disabled;
+                bool isEnabled = request.m_sourceControlEnabled;
+                if (wasEnabled != isEnabled)
+                {
+                    AzToolsFramework::SourceControlConnectionRequestBus::Broadcast(&AzToolsFramework::SourceControlConnectionRequestBus::Events::EnableSourceControl, isEnabled);
+                }
+            }
+        };
+        m_connectionManager->RegisterService(AzFramework::AssetSystem::UpdateSourceControlStatusRequest::MessageType, refreshSourceControl);
     }
     }
 }
 }
 
 

+ 1 - 0
Gems/EMotionFX/Code/Tests/Editor/MotionSetLoadEscalation.cpp

@@ -102,6 +102,7 @@ namespace EMotionFX
         MOCK_METHOD1(SetBranchToken, void (const AZStd::string&));
         MOCK_METHOD1(SetBranchToken, void (const AZStd::string&));
         MOCK_METHOD1(SetProjectName, void (const AZStd::string&));
         MOCK_METHOD1(SetProjectName, void (const AZStd::string&));
         MOCK_METHOD0(ShowAssetProcessor, void());
         MOCK_METHOD0(ShowAssetProcessor, void());
+        MOCK_METHOD1(UpdateSourceControlStatus, void (bool));
         MOCK_METHOD1(ShowInAssetProcessor, void(const AZStd::string&));
         MOCK_METHOD1(ShowInAssetProcessor, void(const AZStd::string&));
         MOCK_METHOD1(WaitUntilAssetProcessorReady, bool(AZStd::chrono::duration<float>));
         MOCK_METHOD1(WaitUntilAssetProcessorReady, bool(AZStd::chrono::duration<float>));
         MOCK_METHOD1(WaitUntilAssetProcessorConnected, bool(AZStd::chrono::duration<float>));
         MOCK_METHOD1(WaitUntilAssetProcessorConnected, bool(AZStd::chrono::duration<float>));