diff --git a/Gems/ScriptCanvas/Code/Editor/Components/GraphUpgrade.cpp b/Gems/ScriptCanvas/Code/Editor/Components/GraphUpgrade.cpp index e87c82e186..2754cc9777 100644 --- a/Gems/ScriptCanvas/Code/Editor/Components/GraphUpgrade.cpp +++ b/Gems/ScriptCanvas/Code/Editor/Components/GraphUpgrade.cpp @@ -686,7 +686,7 @@ namespace ScriptCanvasEditor void EditorGraphUpgradeMachine::OnComplete(IState::ExitStatus exitStatus) { - UpgradeNotifications::Bus::Broadcast(&UpgradeNotifications::OnGraphUpgradeComplete, m_asset, exitStatus == IState::ExitStatus::Skipped); + UpgradeNotificationsBus::Broadcast(&UpgradeNotifications::OnGraphUpgradeComplete, m_asset, exitStatus == IState::ExitStatus::Skipped); m_asset = {}; } diff --git a/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Bus/EditorScriptCanvasBus.h b/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Bus/EditorScriptCanvasBus.h index 09869ba2eb..7c47c77b3e 100644 --- a/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Bus/EditorScriptCanvasBus.h +++ b/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Bus/EditorScriptCanvasBus.h @@ -236,8 +236,6 @@ namespace ScriptCanvasEditor static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; - using Bus = AZ::EBus; - virtual void OnUpgradeStart() {} virtual void OnUpgradeComplete() {} virtual void OnUpgradeCancelled() {} @@ -245,6 +243,5 @@ namespace ScriptCanvasEditor virtual void OnGraphUpgradeComplete(AZ::Data::Asset&, bool skipped = false) { (void)skipped; } }; - - + using UpgradeNotificationsBus = AZ::EBus; } diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp index 390dbaf0fb..4602fe5a87 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp @@ -829,12 +829,12 @@ namespace ScriptCanvasEditor NodePaletteModel::NodePaletteModel() : m_paletteId(AZ::Entity::MakeId()) { - UpgradeNotifications::Bus::Handler::BusConnect(); + UpgradeNotificationsBus::Handler::BusConnect(); } NodePaletteModel::~NodePaletteModel() { - UpgradeNotifications::Bus::Handler::BusDisconnect(); + UpgradeNotificationsBus::Handler::BusDisconnect(); DisconnectLambdas(); diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.h b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.h index b27bfd97f1..f92ab5142e 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.h @@ -61,7 +61,7 @@ namespace ScriptCanvasEditor class NodePaletteModel : public GraphCanvas::CategorizerInterface - , UpgradeNotifications::Bus::Handler + , UpgradeNotificationsBus::Handler { public: typedef AZStd::unordered_map< ScriptCanvas::NodeTypeIdentifier, NodePaletteModelInformation* > NodePaletteRegistry; diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.cpp b/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.cpp index 96b0ac353f..b60b4f2924 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.cpp @@ -148,7 +148,7 @@ namespace ScriptCanvasEditor , m_assetModel(assetModel) , m_categorizer(nodePaletteModel) { - UpgradeNotifications::Bus::Handler::BusConnect(); + UpgradeNotificationsBus::Handler::BusConnect(); if (m_assetModel) { @@ -166,7 +166,7 @@ namespace ScriptCanvasEditor AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); AZ::Data::AssetBus::MultiHandler::BusDisconnect(); - UpgradeNotifications::Bus::Handler::BusDisconnect(); + UpgradeNotificationsBus::Handler::BusDisconnect(); } diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.h b/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.h index 7b2e9293bd..16beb5b79e 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.h @@ -57,7 +57,7 @@ namespace ScriptCanvasEditor : public GraphCanvas::NodePaletteTreeItem , AzFramework::AssetCatalogEventBus::Handler , AZ::Data::AssetBus::MultiHandler - , UpgradeNotifications::Bus::Handler + , UpgradeNotificationsBus::Handler , AZ::SystemTickBus::Handler { public: diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h index 1eeb2dadfb..afc413a425 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h @@ -98,19 +98,19 @@ namespace ScriptCanvasEditor class ScriptCanvasAssetBrowserModel : public AzToolsFramework::AssetBrowser::AssetBrowserFilterModel - , private UpgradeNotifications::Bus::Handler + , private UpgradeNotificationsBus::Handler { public: explicit ScriptCanvasAssetBrowserModel(QObject* parent = nullptr) : AzToolsFramework::AssetBrowser::AssetBrowserFilterModel(parent) { - UpgradeNotifications::Bus::Handler::BusConnect(); + UpgradeNotificationsBus::Handler::BusConnect(); } ~ScriptCanvasAssetBrowserModel() override { - UpgradeNotifications::Bus::Handler::BusDisconnect(); + UpgradeNotificationsBus::Handler::BusDisconnect(); } void OnUpgradeStart() override diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Controller.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Controller.cpp index c8353b1d25..f3ea605451 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Controller.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Controller.cpp @@ -52,9 +52,9 @@ namespace ScriptCanvasEditor m_view->tableWidget->setColumnWidth(3, 22); m_view->textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); m_view->textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn); - connect(m_view->scanButton, &QPushButton::pressed, this, &Controller::OnScanButtonPress); - connect(m_view->closeButton, &QPushButton::pressed, this, &Controller::OnCloseButtonPress); - connect(m_view->upgradeAllButton, &QPushButton::pressed, this, &Controller::OnUpgradeButtonPress); + connect(m_view->scanButton, &QPushButton::pressed, this, &Controller::OnButtonPressScan); + connect(m_view->closeButton, &QPushButton::pressed, this, &Controller::OnButtonPressClose); + connect(m_view->upgradeAllButton, &QPushButton::pressed, this, &Controller::OnButtonPressUpgrade); m_view->progressBar->setValue(0); m_view->progressBar->setVisible(false); @@ -87,12 +87,12 @@ namespace ScriptCanvasEditor LogBus::Broadcast(&LogTraits::Clear); } - void Controller::OnCloseButtonPress() + void Controller::OnButtonPressClose() { reject(); } - void Controller::OnScanButtonPress() + void Controller::OnButtonPressScan() { // \todo move to another file auto isUpToDate = [this](AZ::Data::Asset asset) @@ -128,6 +128,148 @@ namespace ScriptCanvasEditor ModelRequestsBus::Broadcast(&ModelRequestsTraits::Scan, config); } + void Controller::OnButtonPressUpgrade() + { + auto simpleUpdate = [this](AZ::Data::Asset asset) + { + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasAsset* scriptCanvasAsset = asset.GetAs(); + AZ_Assert(scriptCanvasAsset, "Unable to get the asset of ScriptCanvasAsset, but received type: %s" + , azrtti_typeid().template ToString().c_str()); + if (!scriptCanvasAsset) + { + return; + } + + AZ::Entity* scriptCanvasEntity = scriptCanvasAsset->GetScriptCanvasEntity(); + AZ_Assert(scriptCanvasEntity, "View::UpgradeGraph The Script Canvas asset must have a valid entity"); + if (!scriptCanvasEntity) + { + return; + } + + AZ::Entity* queryEntity = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(queryEntity, &AZ::ComponentApplicationRequests::FindEntity, scriptCanvasEntity->GetId()); + if (queryEntity) + { + if (queryEntity->GetState() == AZ::Entity::State::Active) + { + queryEntity->Deactivate(); + } + + scriptCanvasEntity = queryEntity; + } + + if (scriptCanvasEntity->GetState() == AZ::Entity::State::Constructed) + { + scriptCanvasEntity->Init(); + } + + if (scriptCanvasEntity->GetState() == AZ::Entity::State::Init) + { + scriptCanvasEntity->Activate(); + } + + AZ_Assert(scriptCanvasEntity->GetState() == AZ::Entity::State::Active, "Graph entity is not active"); + auto graphComponent = scriptCanvasEntity->FindComponent(); + AZ_Assert(graphComponent, "The Script Canvas entity must have a Graph component"); + if (graphComponent) + { + graphComponent->UpgradeGraph + ( asset + , m_view->forceUpgrade->isChecked() ? Graph::UpgradeRequest::Forced : Graph::UpgradeRequest::IfOutOfDate + , m_view->verbose->isChecked()); + } + } + }; + + auto onReadyOnlyFile = [this]()->bool + { + int result = QMessageBox::No; + QMessageBox mb + ( QMessageBox::Warning + , QObject::tr("Failed to Save Upgraded File") + , QObject::tr("The upgraded file could not be saved because the file is read only.\n" + "Do you want to make it writeable and overwrite it?") + , QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No + , this); + result = mb.exec(); + return result == QMessageBox::YesToAll; + }; + + ModifyConfiguration config; + config.modification = simpleUpdate; + config.onReadOnlyFile = onReadyOnlyFile; + config.backupGraphBeforeModification = m_view->makeBackupCheckbox->isChecked(); + ModelRequestsBus::Broadcast(&ModelRequestsTraits::Modify, config); + } + + void Controller::OnUpgradeModificationBegin([[maybe_unused]] const ModifyConfiguration& config, const AZ::Data::AssetInfo& info) + { + QList items = m_view->tableWidget->findItems(info.m_relativePath.c_str(), Qt::MatchFlag::MatchExactly); + if (!items.isEmpty()) + { + for (auto* item : items) + { + int row = item->row(); + SetRowBusy(row); + } + } + } + + void Controller::OnUpgradeModificationEnd + ( [[maybe_unused]] const ModifyConfiguration& config + , const AZ::Data::AssetInfo& info + , ModificationResult result) + { + if (result.errorMessage.empty()) + { + VE_LOG("Successfully modified %s", result.assetInfo.m_relativePath.c_str()); + } + else + { + VE_LOG("Failed to modify %s: %s", result.assetInfo.m_relativePath.c_str(), result.errorMessage.data()); + } + + QList items = m_view->tableWidget->findItems(info.m_relativePath.c_str(), Qt::MatchFlag::MatchExactly); + if (!items.isEmpty()) + { + for (auto* item : items) + { + int row = item->row(); + + if (result.errorMessage.empty()) + { + SetRowSucceeded(row); + } + else + { + SetRowFailed(row, ""); + } + } + } + + m_view->progressBar->setVisible(true); + ++m_handledAssetCount; + m_view->progressBar->setValue(m_handledAssetCount); + } + + void Controller::OnGraphUpgradeComplete(AZ::Data::Asset& asset, bool skipped) + { + ModificationResult result; + result.asset = asset; + AZ::Data::AssetCatalogRequestBus::BroadcastResult + ( result.assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, asset.GetId()); + + if (skipped) + { + result.errorMessage = "Failed in editor upgrade state machine - check logs"; + } + + ModificationNotificationsBus::Broadcast(&ModificationNotificationsTraits::ModificationComplete, result); + } + void Controller::OnScanBegin(size_t assetCount) { m_handledAssetCount = 0; @@ -264,68 +406,6 @@ namespace ScriptCanvasEditor OnScannedGraph(info, Filtered::No); } - void Controller::OnUpgradeButtonPress() - { - auto simpleUpdate = [this](AZ::Data::Asset asset) - { - if (asset.GetType() == azrtti_typeid()) - { - ScriptCanvasAsset* scriptCanvasAsset = asset.GetAs(); - AZ_Assert(scriptCanvasAsset, "Unable to get the asset of ScriptCanvasAsset, but received type: %s" - , azrtti_typeid().template ToString().c_str()); - if (!scriptCanvasAsset) - { - return; - } - - AZ::Entity* scriptCanvasEntity = scriptCanvasAsset->GetScriptCanvasEntity(); - AZ_Assert(scriptCanvasEntity, "View::UpgradeGraph The Script Canvas asset must have a valid entity"); - if (!scriptCanvasEntity) - { - return; - } - - AZ::Entity* queryEntity = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(queryEntity, &AZ::ComponentApplicationRequests::FindEntity, scriptCanvasEntity->GetId()); - if (queryEntity) - { - if (queryEntity->GetState() == AZ::Entity::State::Active) - { - queryEntity->Deactivate(); - } - - scriptCanvasEntity = queryEntity; - } - - if (scriptCanvasEntity->GetState() == AZ::Entity::State::Constructed) - { - scriptCanvasEntity->Init(); - } - - if (scriptCanvasEntity->GetState() == AZ::Entity::State::Init) - { - scriptCanvasEntity->Activate(); - } - - AZ_Assert(scriptCanvasEntity->GetState() == AZ::Entity::State::Active, "Graph entity is not active"); - auto graphComponent = scriptCanvasEntity->FindComponent(); - AZ_Assert(graphComponent, "The Script Canvas entity must have a Graph component"); - if (graphComponent) - { - graphComponent->UpgradeGraph - ( asset - , m_view->forceUpgrade->isChecked() ? Graph::UpgradeRequest::Forced : Graph::UpgradeRequest::IfOutOfDate - , m_view->verbose->isChecked()); - } - } - }; - - ModifyConfiguration config; - config.modification = simpleUpdate; - config.backupGraphBeforeModification = m_view->makeBackupCheckbox->isChecked(); - ModelRequestsBus::Broadcast(&ModelRequestsTraits::Modify, config); - } - void Controller::OnUpgradeBegin ( const ModifyConfiguration& config , [[maybe_unused]] const AZStd::vector& assets) @@ -359,8 +439,13 @@ namespace ScriptCanvasEditor m_view->spinner->SetBusyIconSize(16); } - void Controller::OnUpgradeComplete() + void Controller::OnUpgradeComplete(const ModificationResults& result) { + QString spinnerText = QStringLiteral("Upgrade Complete - "); + spinnerText.append(QString::asprintf(" - Upgraded: %zu, Failed: %zu" + , result.m_successes.size() + , result.m_failures.size())); + m_view->spinner->SetText(spinnerText); SetSpinnerIsBusy(false); } diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Controller.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Controller.h index f99aaf5466..1b8b109759 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Controller.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Controller.h @@ -41,7 +41,7 @@ namespace ScriptCanvasEditor //! Handles display change notifications, handles state change notifications, sends control requests class Controller : public AzQtComponents::StyledDialog - , private UpgradeNotifications::Bus::Handler + , private UpgradeNotificationsBus::Handler , private ModelNotificationsBus::Handler { Q_OBJECT @@ -62,10 +62,12 @@ namespace ScriptCanvasEditor void AddLogEntries(); - void OnCloseButtonPress(); - void OnScanButtonPress(); - void OnUpgradeButtonPress(); + void OnButtonPressClose(); + void OnButtonPressScan(); + void OnButtonPressUpgrade(); + void OnGraphUpgradeComplete(AZ::Data::Asset&, bool skipped) override; + void OnScanBegin(size_t assetCount) override; void OnScanComplete(const ScanResult& result) override; void OnScanFilteredGraph(const AZ::Data::AssetInfo& info) override; @@ -77,7 +79,7 @@ namespace ScriptCanvasEditor // for single operation UI updates, just check the assets size, or note it on the request void OnUpgradeBegin(const ModifyConfiguration& config, const AZStd::vector& assets) override; - void OnUpgradeComplete() override; + void OnUpgradeComplete(const ModificationResults& results) override; void OnUpgradeDependenciesGathered(const AZ::Data::AssetInfo& info, Result result) override; void OnUpgradeDependencySortBegin ( const ModifyConfiguration& config @@ -86,6 +88,8 @@ namespace ScriptCanvasEditor ( const ModifyConfiguration& config , const AZStd::vector& assets , const AZStd::vector& sortedOrder) override; + void OnUpgradeModificationBegin(const ModifyConfiguration& config, const AZ::Data::AssetInfo& info) override; + void OnUpgradeModificationEnd(const ModifyConfiguration& config, const AZ::Data::AssetInfo& info, ModificationResult result) override; void SetSpinnerIsBusy(bool isBusy); void SetRowBusy(int index); diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.cpp index 7a7c4fe5a3..f067feeca4 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.cpp @@ -6,8 +6,15 @@ * */ +#include +#include #include +#include +#include +#include +#include #include +#include namespace FileSaverCpp { @@ -44,6 +51,183 @@ namespace ScriptCanvasEditor { namespace VersionExplorer { + FileSaver::FileSaver + ( AZStd::function onReadOnlyFile + , AZStd::function onComplete) + : m_onReadOnlyFile(onReadOnlyFile) + , m_onComplete(onComplete) + {} + void FileSaver::PerformMove + ( AZStd::string tmpFileName + , AZStd::string target + , size_t remainingAttempts) + { + FileSaverCpp::FileEventHandler fileEventHandler; + + if (remainingAttempts == 0) + { + AZ::SystemTickBus::QueueFunction([this, tmpFileName]() + { + FileSaveResult result; + result.fileSaveError = "Failed to move updated file from temporary location to tmpFileName destination"; + result.tempFileRemovalError = RemoveTempFile(tmpFileName); + m_onComplete(result); + }); + } + else if (remainingAttempts == 2) + { + auto streamer = AZ::Interface::Get(); + // before the last attempt, flush all the caches + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCaches(); + streamer->SetRequestCompleteCallback(flushRequest + , [this, remainingAttempts, tmpFileName, target]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + // One last try + AZ::SystemTickBus::QueueFunction( + [this, remainingAttempts, tmpFileName, target]() { PerformMove(tmpFileName, target, remainingAttempts - 1); }); + }); + streamer->QueueRequest(flushRequest); + } + else + { + // the actual move attempt + auto moveResult = AZ::IO::SmartMove(tmpFileName.c_str(), target.c_str()); + if (moveResult.GetResultCode() == AZ::IO::ResultCode::Success) + { + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); + // Bump the slice asset up in the asset processor's queue. + AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, target.c_str()); + AZ::SystemTickBus::QueueFunction([this, tmpFileName]() + { + FileSaveResult result; + result.tempFileRemovalError = RemoveTempFile(tmpFileName); + m_onComplete(result); + }); + } + else + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to tmpFileName destination failed: %s, trying again", target.c_str()); + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); + streamer->SetRequestCompleteCallback(flushRequest, [this, tmpFileName, target, remainingAttempts]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + // Continue saving. + AZ::SystemTickBus::QueueFunction([this, tmpFileName, target, remainingAttempts]() { PerformMove(tmpFileName, target, remainingAttempts - 1); }); + }); + streamer->QueueRequest(flushRequest); + } + } + } + + void FileSaver::OnSourceFileReleased(AZ::Data::Asset asset) + { + AZStd::string relativePath, fullPath; + AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); + bool fullPathFound = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, fullPath); + AZStd::string tmpFileName; + // here we are saving the graph to a temp file instead of the original file and then copying the temp file to the original file. + // This ensures that AP will not a get a file change notification on an incomplete graph file causing it to fail processing. Temp files are ignored by AP. + if (!AZ::IO::CreateTempFileName(fullPath.c_str(), tmpFileName)) + { + FileSaveResult result; + result.fileSaveError = "Failure to create temporary file name"; + m_onComplete(result); + return; + } + + bool tempSavedSucceeded = false; + AZ::IO::FileIOStream fileStream(tmpFileName.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeText); + if (fileStream.IsOpen()) + { + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasEditor::ScriptCanvasAssetHandler handler; + tempSavedSucceeded = handler.SaveAssetData(asset, &fileStream); + } + + fileStream.Close(); + } + + if (!tempSavedSucceeded) + { + FileSaveResult result; + result.fileSaveError = "Save asset data to temporary file failed"; + m_onComplete(result); + return; + } + + AzToolsFramework::SourceControlCommandBus::Broadcast + ( &AzToolsFramework::SourceControlCommandBus::Events::RequestEdit + , fullPath.c_str() + , true + , [this, fullPath, tmpFileName]([[maybe_unused]] bool success, const AzToolsFramework::SourceControlFileInfo& info) + { + constexpr const size_t k_maxAttemps = 10; + + if (!info.IsReadOnly()) + { + PerformMove(tmpFileName, fullPath, k_maxAttemps); + } + else if (m_onReadOnlyFile && m_onReadOnlyFile()) + { + AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true); + PerformMove(tmpFileName, fullPath, k_maxAttemps); + } + else + { + FileSaveResult result; + result.fileSaveError = "Source file was and remained read-only"; + result.tempFileRemovalError = RemoveTempFile(tmpFileName); + m_onComplete(result); + } + }); + } + + AZStd::string FileSaver::RemoveTempFile(AZStd::string_view tempFile) + { + AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); + if (!fileIO) + { + return "GraphUpgradeComplete: No FileIO instance"; + } + + if (fileIO->Exists(tempFile.data()) && !fileIO->Remove(tempFile.data())) + { + return AZStd::string::format("Failed to remove temporary file: %s", tempFile.data()); + } + + return ""; + } + + void FileSaver::Save(AZ::Data::Asset asset) + { + AZStd::string relativePath, fullPath; + AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); + bool fullPathFound = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult + (fullPathFound + , &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath + , relativePath, fullPath); + + if (!fullPathFound) + { + FileSaveResult result; + result.fileSaveError = "Full source path not found"; + m_onComplete(result); + } + else + { + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(fullPath); + streamer->SetRequestCompleteCallback(flushRequest, [this, asset]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + this->OnSourceFileReleased(asset); + }); + streamer->QueueRequest(flushRequest); + } + } } } diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.h index e1ab92acbc..dd333927ed 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.h @@ -14,10 +14,35 @@ namespace ScriptCanvasEditor { namespace VersionExplorer { + struct FileSaveResult + { + AZStd::string fileSaveError; + AZStd::string tempFileRemovalError; + }; + class FileSaver { public: AZ_CLASS_ALLOCATOR(FileSaver, AZ::SystemAllocator, 0); + + FileSaver + ( AZStd::function onReadOnlyFile + , AZStd::function onComplete); + + void Save(AZ::Data::Asset asset); + + private: + AZStd::function m_onComplete; + AZStd::function m_onReadOnlyFile; + + void OnSourceFileReleased(AZ::Data::Asset asset); + + void PerformMove + ( AZStd::string source + , AZStd::string target + , size_t remainingAttempts); + + AZStd::string RemoveTempFile(AZStd::string_view tempFile); }; } } diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Model.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Model.cpp index ad9d39cb98..b88d147d6d 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Model.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Model.cpp @@ -103,7 +103,7 @@ namespace ScriptCanvasEditor void Model::OnModificationComplete() { - ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnScanComplete, m_scanner->GetResult()); + ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnUpgradeComplete, m_modifier->GetResult()); m_modifier.reset(); m_scanner.reset(); Idle(); diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Model.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Model.h index 72ab210e37..637c77648a 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Model.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Model.h @@ -57,7 +57,8 @@ namespace ScriptCanvasEditor State m_state = State::Idle; Log m_log; - + + // these two are managed by the same class because the modifer will only operate on the results of the scanner AZStd::unique_ptr m_modifier; AZStd::unique_ptr m_scanner; AZStd::unique_ptr m_settingsCache; diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/ModelTraits.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/ModelTraits.h index 86e1b47b18..6eedba647f 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/ModelTraits.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/ModelTraits.h @@ -17,11 +17,27 @@ namespace ScriptCanvasEditor struct ModifyConfiguration { AZStd::function)> modification; + AZStd::function onReadOnlyFile; bool modifySingleAsset = false; bool backupGraphBeforeModification = false; bool successfulDependencyUpgradeRequired = true; }; + struct ModificationResult + { + AZ::Data::Asset asset; + AZ::Data::AssetInfo assetInfo; + AZStd::string errorMessage; + }; + + class ModificationNotificationsTraits + : public AZ::EBusTraits + { + public: + virtual void ModificationComplete(const ModificationResult& result) = 0; + }; + using ModificationNotificationsBus = AZ::EBus; + struct ScanConfiguration { AZStd::function)> filter; @@ -37,9 +53,10 @@ namespace ScriptCanvasEditor }; using ModelRequestsBus = AZ::EBus; - struct ModificationResult + struct ModificationResults { - + AZStd::vector m_successes; + AZStd::vector m_failures; }; struct ScanResult @@ -67,9 +84,7 @@ namespace ScriptCanvasEditor virtual void OnScanUnFilteredGraph(const AZ::Data::AssetInfo& info) = 0; virtual void OnUpgradeBegin(const ModifyConfiguration& config, const AZStd::vector& assets) = 0; - virtual void OnUpgradeComplete() = 0; - // virtual void OnUpgradeModificationBegin(const ModifyConfiguration& config) = 0; - // virtual void OnUpgradeModification(const ModifyConfiguration& config, const AZ::Data::AssetInfo& info, Result result) = 0; + virtual void OnUpgradeComplete(const ModificationResults& results) = 0; virtual void OnUpgradeDependenciesGathered(const AZ::Data::AssetInfo& info, Result result) = 0; virtual void OnUpgradeDependencySortBegin ( const ModifyConfiguration& config @@ -78,6 +93,8 @@ namespace ScriptCanvasEditor ( const ModifyConfiguration& config , const AZStd::vector& assets , const AZStd::vector& sortedOrder) = 0; + virtual void OnUpgradeModificationBegin(const ModifyConfiguration& config, const AZ::Data::AssetInfo& info) = 0; + virtual void OnUpgradeModificationEnd(const ModifyConfiguration& config, const AZ::Data::AssetInfo& info, ModificationResult result) = 0; }; using ModelNotificationsBus = AZ::EBus; } diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.cpp index 5656d6cf6d..4096508b8b 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.cpp @@ -30,6 +30,7 @@ namespace ScriptCanvasEditor , m_assets(assets) , m_onComplete(onComplete) { + AZ_Assert(m_config.modification, "No modification function provided"); ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnUpgradeBegin, modification, m_assets); AZ::SystemTickBus::Handler::BusConnect(); } @@ -52,6 +53,11 @@ namespace ScriptCanvasEditor return iter->second; } + const ModificationResults& Modifier::GetResult() const + { + return m_results; + } + void Modifier::GatherDependencies() { AZ::SerializeContext* serializeContext{}; @@ -140,6 +146,84 @@ namespace ScriptCanvasEditor } } + void Modifier::ModificationComplete(const ModificationResult& result) + { + m_result = result; + + if (result.errorMessage.empty()) + { + SaveModifiedGraph(result); + } + else + { + ReportModificationError(result.errorMessage); + } + } + + void Modifier::ModifyCurrentAsset() + { + m_result = {}; + m_result.assetInfo = GetCurrentAsset(); + + ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnUpgradeModificationBegin, m_config, GetCurrentAsset()); + + if (auto asset = LoadAsset()) + { + ModificationNotificationsBus::Handler::BusConnect(); + m_modifyState = ModifyState::InProgress; + m_config.modification(asset); + } + else + { + ReportModificationError("Failed to load during modification"); + } + } + + void Modifier::ModifyNextAsset() + { + ModelNotificationsBus::Broadcast + ( &ModelNotificationsTraits::OnUpgradeModificationEnd, m_config, GetCurrentAsset(), m_result); + m_modifyState = ModifyState::Idle; + ++m_assetIndex; + m_result = {}; + } + + void Modifier::ReportModificationError(AZStd::string_view report) + { + m_result.asset = {}; + m_result.errorMessage = report; + m_results.m_failures.push_back(m_result); + ModifyNextAsset(); + } + + void Modifier::ReportModificationSuccess() + { + m_results.m_successes.push_back(m_result.assetInfo); + ModifyNextAsset(); + } + + void Modifier::OnFileSaveComplete(const FileSaveResult& result) + { + if (!result.tempFileRemovalError.empty()) + { + VE_LOG + ( "Temporary file not removed for %s: %s" + , m_result.assetInfo.m_relativePath.c_str() + , result.tempFileRemovalError.c_str()); + } + + m_fileSaver.reset(); + + if (result.fileSaveError.empty()) + { + ReportModificationSuccess(); + } + else + { + ReportModificationError(result.fileSaveError); + } + } + void Modifier::OnSystemTick() { switch (m_state) @@ -154,50 +238,13 @@ namespace ScriptCanvasEditor } } - const AZStd::unordered_set* Modifier::Sorter::GetDependencies(size_t index) const + void Modifier::SaveModifiedGraph(const ModificationResult& result) { - auto iter = modifier->m_dependencies.find(index); - return iter != modifier->m_dependencies.end() ? &iter->second : nullptr; - } - - void Modifier::Sorter::Sort() - { - for (size_t index = 0; index != modifier->m_assets.size(); ++index) - { - Visit(index); - } - } - - void Modifier::Sorter::Visit(size_t index) - { - if (markedPermanent.contains(index)) - { - return; - } - - if (markedTemporary.contains(index)) - { - AZ_Error - ( ScriptCanvas::k_VersionExplorerWindow.data() - , false - , "Modifier: Dependency sort has failed during, circular dependency detected for Asset: %s" - , modifier->GetCurrentAsset().m_relativePath.c_str()); - return; - } - - markedTemporary.insert(index); - - if (auto dependencies = GetDependencies(index)) - { - for (auto& dependency : *dependencies) - { - Visit(dependency); - } - } - - markedTemporary.erase(index); - markedPermanent.insert(index); - modifier->m_dependencyOrderedAssetIndicies.push_back(index); + m_modifyState = ModifyState::Saving; + m_fileSaver = AZStd::make_unique + ( m_config.onReadOnlyFile + , [this](const FileSaveResult& result) { OnFileSaveComplete(result); }); + m_fileSaver->Save(result.asset); } void Modifier::SortGraphsByDependencies() @@ -208,6 +255,11 @@ namespace ScriptCanvasEditor sorter.Sort(); } + ModificationResults&& Modifier::TakeResult() + { + return AZStd::move(m_results); + } + void Modifier::TickGatherDependencies() { if (m_assetIndex == 0) @@ -260,8 +312,69 @@ namespace ScriptCanvasEditor void Modifier::TickUpdateGraph() { + if (m_assetIndex == m_assets.size()) + { + VE_LOG("Modifier: Complete."); + AZ::SystemTickBus::Handler::BusDisconnect(); + if (m_onComplete) + { + m_onComplete(); + } + } + else + { + if (m_modifyState == ModifyState::Idle) + { + ModifyCurrentAsset(); + } + } } + const AZStd::unordered_set* Modifier::Sorter::GetDependencies(size_t index) const + { + auto iter = modifier->m_dependencies.find(index); + return iter != modifier->m_dependencies.end() ? &iter->second : nullptr; + } + + void Modifier::Sorter::Sort() + { + for (size_t index = 0; index != modifier->m_assets.size(); ++index) + { + Visit(index); + } + } + + void Modifier::Sorter::Visit(size_t index) + { + if (markedPermanent.contains(index)) + { + return; + } + + if (markedTemporary.contains(index)) + { + AZ_Error + (ScriptCanvas::k_VersionExplorerWindow.data() + , false + , "Modifier: Dependency sort has failed during, circular dependency detected for Asset: %s" + , modifier->GetCurrentAsset().m_relativePath.c_str()); + return; + } + + markedTemporary.insert(index); + + if (auto dependencies = GetDependencies(index)) + { + for (auto& dependency : *dependencies) + { + Visit(dependency); + } + } + + markedTemporary.erase(index); + markedPermanent.insert(index); + modifier->m_dependencyOrderedAssetIndicies.push_back(index); + } } } diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.h index e0b1f1a289..f318627cbe 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.h @@ -8,9 +8,10 @@ #pragma once -#include -#include #include +#include +#include +#include namespace ScriptCanvasEditor { @@ -18,6 +19,7 @@ namespace ScriptCanvasEditor { class Modifier : private AZ::SystemTickBus::Handler + , private ModificationNotificationsBus::Handler { public: AZ_CLASS_ALLOCATOR(Modifier, AZ::SystemAllocator, 0); @@ -27,6 +29,9 @@ namespace ScriptCanvasEditor , AZStd::vector&& assets , AZStd::function onComplete); + const ModificationResults& GetResult() const; + ModificationResults&& TakeResult(); + private: friend class Sorter; @@ -48,7 +53,16 @@ namespace ScriptCanvasEditor ModifyingGraphs }; + enum class ModifyState + { + Idle, + InProgress, + Saving, + }; + + // the two states reside in this class because the modification is only complete if the new source file saves out State m_state = State::GatheringDependencies; + ModifyState m_modifyState = ModifyState::Idle; size_t m_assetIndex = 0; AZStd::function m_onComplete; // asset infos in scanned order @@ -61,12 +75,21 @@ namespace ScriptCanvasEditor AZStd::vector m_failures; ModifyConfiguration m_config; ModificationResult m_result; + ModificationResults m_results; + AZStd::unique_ptr m_fileSaver; void GatherDependencies(); const AZ::Data::AssetInfo& GetCurrentAsset() const; AZStd::unordered_set& GetOrCreateDependencyIndexSet(); AZ::Data::Asset LoadAsset(); + void ModifyCurrentAsset(); + void ModifyNextAsset(); + void ModificationComplete(const ModificationResult& result) override; + void ReportModificationError(AZStd::string_view report); + void ReportModificationSuccess(); + void SaveModifiedGraph(const ModificationResult& result); void SortGraphsByDependencies(); + void OnFileSaveComplete(const FileSaveResult& result); void OnSystemTick() override; void TickGatherDependencies(); void TickUpdateGraph(); diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.h index 8b4dd69c82..fb030845c7 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.h @@ -25,7 +25,6 @@ namespace ScriptCanvasEditor Scanner(const ScanConfiguration& config, AZStd::function onComplete); const ScanResult& GetResult() const; - ScanResult&& TakeResult(); private: